Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit 8b27827

Browse files
committed
tests: add tests
1 parent 8bc9dda commit 8b27827

6 files changed

Lines changed: 160 additions & 23 deletions

File tree

gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
210210
self._transport = Transport(
211211
credentials=credentials,
212212
credentials_file=client_options.credentials_file,
213-
host=self.DEFAULT_ENDPOINT,
213+
host=client_options.api_endpoint,
214214
scopes=client_options.scopes,
215215
api_mtls_endpoint=client_options.api_endpoint,
216216
client_cert_source=client_options.client_cert_source,

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc.py.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class {{ service.name }}GrpcTransport({{ service.name }}Transport):
110110
)
111111

112112
# Run the base constructor.
113-
super().__init__(host=host, credentials=credentials, credentials_file=credentials_file, scopes=scopes)
113+
super().__init__(host=host, credentials=credentials, credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES)
114114
self._stubs = {} # type: Dict[str, Callable]
115115

116116
@classmethod

gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/grpc_asyncio.py.j2

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
4343
def create_channel(cls,
4444
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
4545
credentials: credentials.Credentials = None,
46+
credentials_file: Optional[str] = None,
4647
scopes: Optional[Sequence[str]] = None,
4748
**kwargs) -> aio.Channel:
4849
"""Create and return a gRPC AsyncIO channel object.
@@ -53,6 +54,9 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
5354
credentials identify this application to the service. If
5455
none are specified, the client will attempt to ascertain
5556
the credentials from the environment.
57+
credentials_file (Optional[str]): A file with credentials that can
58+
be loaded with :func:`google.auth.load_credentials_from_file`.
59+
This argument is ignored if ``channel`` is provided.
5660
scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
5761
service. These are only used when credentials are not specified and
5862
are passed to :func:`google.auth.default`.
@@ -65,13 +69,16 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
6569
return grpc_helpers_async.create_channel(
6670
host,
6771
credentials=credentials,
72+
credentials_file=credentials_file,
6873
scopes=scopes,
6974
**kwargs
7075
)
7176

7277
def __init__(self, *,
7378
host: str{% if service.host %} = '{{ service.host }}'{% endif %},
7479
credentials: credentials.Credentials = None,
80+
credentials_file: Optional[str] = None,
81+
scopes: Optional[Sequence[str]] = None,
7582
channel: aio.Channel = None,
7683
api_mtls_endpoint: str = None,
7784
client_cert_source: Callable[[], Tuple[bytes, bytes]] = None) -> None:
@@ -86,6 +93,12 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
8693
are specified, the client will attempt to ascertain the
8794
credentials from the environment.
8895
This argument is ignored if ``channel`` is provided.
96+
credentials_file (Optional[str]): A file with credentials that can
97+
be loaded with :func:`google.auth.load_credentials_from_file`.
98+
This argument is ignored if ``channel`` is provided.
99+
scopes (Optional[Sequence[str]]): A optional list of scopes needed for this
100+
service. These are only used when credentials are not specified and
101+
are passed to :func:`google.auth.default`.
89102
channel (Optional[aio.Channel]): A ``Channel`` instance through
90103
which to make calls.
91104
api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If
@@ -98,8 +111,9 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
98111
is None.
99112

100113
Raises:
101-
google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
114+
google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport
102115
creation failed for any reason.
116+
ValueError: If both ``credentials`` and ``credentials_file`` are passed.
103117
"""
104118
if channel:
105119
# Sanity check: Ensure that channel and credentials are not both
@@ -125,12 +139,13 @@ class {{ service.grpc_asyncio_transport_name }}({{ service.name }}Transport):
125139
self._grpc_channel = type(self).create_channel(
126140
host,
127141
credentials=credentials,
142+
credentials_file=credentials_file,
128143
ssl_credentials=ssl_credentials,
129-
scopes=self.AUTH_SCOPES,
144+
scopes=scopes or self.AUTH_SCOPES,
130145
)
131146

132147
# Run the base constructor.
133-
super().__init__(host=host, credentials=credentials)
148+
super().__init__(host=host, credentials=credentials, credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES)
134149
self._stubs = {}
135150

136151
@property

gapic/templates/noxfile.py.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ def unit(session):
1313
session.install('coverage', 'pytest', 'pytest-cov', 'asyncmock', 'pytest-asyncio')
1414
session.install('-e', '.')
1515

16+
# TODO(busunkim): Remove once these PRs are merged
17+
session.install('--force-reinstall', 'git+https://github.com/googleapis/python-api-core.git@creds-and-scope-overrides')
18+
session.install('--force-reinstall', 'git+https://github.com/googleapis/google-auth-library-python.git@support-scopes')
19+
1620
session.run(
1721
'py.test',
1822
'--quiet',

gapic/templates/tests/unit/gapic/%name_%version/%sub/test_%service.py.j2

Lines changed: 126 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,12 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
101101
patched.return_value = None
102102
client = client_class(client_options=options)
103103
patched.assert_called_once_with(
104-
api_mtls_endpoint="squid.clam.whelk",
105-
client_cert_source=None,
106104
credentials=None,
105+
credentials_file=None,
107106
host="squid.clam.whelk",
107+
scopes=None,
108+
api_mtls_endpoint="squid.clam.whelk",
109+
client_cert_source=None,
108110
)
109111

110112
# Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
@@ -114,10 +116,12 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
114116
patched.return_value = None
115117
client = client_class()
116118
patched.assert_called_once_with(
117-
api_mtls_endpoint=client.DEFAULT_ENDPOINT,
118-
client_cert_source=None,
119119
credentials=None,
120+
credentials_file=None,
120121
host=client.DEFAULT_ENDPOINT,
122+
scopes=None,
123+
api_mtls_endpoint=client.DEFAULT_ENDPOINT,
124+
client_cert_source=None,
121125
)
122126

123127
# Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is
@@ -127,10 +131,12 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
127131
patched.return_value = None
128132
client = client_class()
129133
patched.assert_called_once_with(
130-
api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
131-
client_cert_source=None,
132134
credentials=None,
135+
credentials_file=None,
133136
host=client.DEFAULT_MTLS_ENDPOINT,
137+
scopes=None,
138+
api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
139+
client_cert_source=None,
134140
)
135141

136142
# Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
@@ -141,10 +147,13 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
141147
patched.return_value = None
142148
client = client_class(client_options=options)
143149
patched.assert_called_once_with(
144-
api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
145-
client_cert_source=client_cert_source_callback,
146150
credentials=None,
151+
credentials_file=None,
147152
host=client.DEFAULT_MTLS_ENDPOINT,
153+
scopes=None,
154+
api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
155+
client_cert_source=client_cert_source_callback,
156+
148157
)
149158

150159
# Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
@@ -155,10 +164,12 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
155164
patched.return_value = None
156165
client = client_class()
157166
patched.assert_called_once_with(
158-
api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
159-
client_cert_source=None,
160167
credentials=None,
168+
credentials_file=None,
161169
host=client.DEFAULT_MTLS_ENDPOINT,
170+
scopes=None,
171+
api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT,
172+
client_cert_source=None,
162173
)
163174

164175
# Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is
@@ -169,10 +180,12 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
169180
patched.return_value = None
170181
client = client_class()
171182
patched.assert_called_once_with(
172-
api_mtls_endpoint=client.DEFAULT_ENDPOINT,
173-
client_cert_source=None,
174183
credentials=None,
184+
credentials_file=None,
175185
host=client.DEFAULT_ENDPOINT,
186+
scopes=None,
187+
api_mtls_endpoint=client.DEFAULT_ENDPOINT,
188+
client_cert_source=None,
176189
)
177190

178191
# Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has
@@ -184,17 +197,63 @@ def test_{{ service.client_name|snake_case }}_client_options(client_class, trans
184197
del os.environ["GOOGLE_API_USE_MTLS"]
185198

186199

200+
@pytest.mark.parametrize("client_class,transport_class,transport_name", [
201+
({{ service.client_name }}, transports.{{ service.grpc_transport_name }}, "grpc"),
202+
({{ service.async_client_name }}, transports.{{ service.grpc_asyncio_transport_name }}, "grpc_asyncio")
203+
])
204+
def test_{{ service.client_name|snake_case }}_client_options_scopes(client_class, transport_class, transport_name):
205+
# Check the case api_endpoint is provided.
206+
options = client_options.ClientOptions(
207+
scopes=["1", "2"],
208+
)
209+
with mock.patch.object(transport_class, '__init__') as patched:
210+
patched.return_value = None
211+
client = client_class(client_options=options)
212+
patched.assert_called_once_with(
213+
credentials=None,
214+
credentials_file=None,
215+
host="localhost:7469",
216+
scopes=["1", "2"],
217+
api_mtls_endpoint="localhost:7469",
218+
client_cert_source=None,
219+
)
220+
221+
222+
@pytest.mark.parametrize("client_class,transport_class,transport_name", [
223+
({{ service.client_name }}, transports.{{ service.grpc_transport_name }}, "grpc"),
224+
({{ service.async_client_name }}, transports.{{ service.grpc_asyncio_transport_name }}, "grpc_asyncio")
225+
])
226+
def test_{{ service.client_name|snake_case }}_client_options_credentials_file(client_class, transport_class, transport_name):
227+
# Check the case api_endpoint is provided.
228+
options = client_options.ClientOptions(
229+
credentials_file="credentials.json"
230+
)
231+
with mock.patch.object(transport_class, '__init__') as patched:
232+
patched.return_value = None
233+
client = client_class(client_options=options)
234+
patched.assert_called_once_with(
235+
credentials=None,
236+
credentials_file="credentials.json",
237+
host="localhost:7469",
238+
scopes=None,
239+
api_mtls_endpoint="localhost:7469",
240+
client_cert_source=None,
241+
)
242+
243+
187244
def test_{{ service.client_name|snake_case }}_client_options_from_dict():
188245
with mock.patch('{{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join(".") }}.services.{{ service.name|snake_case }}.transports.{{ service.name }}GrpcTransport.__init__') as grpc_transport:
189246
grpc_transport.return_value = None
190247
client = {{ service.client_name }}(
191248
client_options={'api_endpoint': 'squid.clam.whelk'}
192249
)
193250
grpc_transport.assert_called_once_with(
194-
api_mtls_endpoint="squid.clam.whelk",
195-
client_cert_source=None,
196251
credentials=None,
252+
credentials_file=None,
197253
host="squid.clam.whelk",
254+
scopes=None,
255+
api_mtls_endpoint="squid.clam.whelk",
256+
client_cert_source=None,
198257
)
199258

200259

@@ -793,6 +852,27 @@ def test_credentials_transport_error():
793852
transport=transport,
794853
)
795854

855+
# It is an error to provide a credentials file and a transport instance.
856+
transport = transports.{{ service.name }}GrpcTransport(
857+
credentials=credentials.AnonymousCredentials(),
858+
)
859+
with pytest.raises(ValueError):
860+
client = {{ service.client_name }}(
861+
client_options={"credentials_file": "credentials.json"},
862+
transport=transport,
863+
)
864+
865+
# It is an error to provide scopes and a transport instance.
866+
transport = transports.{{ service.name }}GrpcTransport(
867+
credentials=credentials.AnonymousCredentials(),
868+
)
869+
with pytest.raises(ValueError):
870+
client = {{ service.client_name }}(
871+
client_options={"scopes": ["1", "2"]},
872+
transport=transport,
873+
)
874+
875+
796876

797877
def test_transport_instance():
798878
# A client may be instantiated with a custom transport instance.
@@ -829,6 +909,16 @@ def test_transport_grpc_default():
829909
)
830910

831911

912+
def test_{{ service.name|snake_case }}_base_transport_error():
913+
# Passing both a credentials object and credentials_file should raise an error
914+
with pytest.raises(ValueError) as excinfo:
915+
transport = transports.{{ service.name }}Transport(
916+
credentials=credentials.AnonymousCredentials(),
917+
credentials_file="credentials.json"
918+
)
919+
assert "mutually exclusive" in str(excinfo.value)
920+
921+
832922
def test_{{ service.name|snake_case }}_base_transport():
833923
# Instantiate the base transport.
834924
transport = transports.{{ service.name }}Transport(
@@ -854,6 +944,20 @@ def test_{{ service.name|snake_case }}_base_transport():
854944
{% endif %}
855945

856946

947+
def test_{{ service.name|snake_case }}_base_transport_with_credentials_file():
948+
# Instantiate the base transport with a credentials file
949+
with mock.patch.object(auth, 'load_credentials_from_file') as load_creds:
950+
load_creds.return_value = (credentials.AnonymousCredentials(), None)
951+
transport = transports.{{ service.name }}Transport(
952+
credentials_file="credentials.json",
953+
)
954+
load_creds.assert_called_once_with("credentials.json", scopes=(
955+
{%- for scope in service.oauth_scopes %}
956+
'{{ scope }}',
957+
{%- endfor %}
958+
))
959+
960+
857961
def test_{{ service.name|snake_case }}_auth_adc():
858962
# If no credentials are provided, we should use ADC credentials.
859963
with mock.patch.object(auth, 'default') as adc:
@@ -960,12 +1064,13 @@ def test_{{ service.name|snake_case }}_grpc_transport_channel_mtls_with_client_c
9601064
grpc_create_channel.assert_called_once_with(
9611065
"mtls.squid.clam.whelk:443",
9621066
credentials=mock_cred,
963-
ssl_credentials=mock_ssl_cred,
1067+
credentials_file=None,
9641068
scopes=(
9651069
{%- for scope in service.oauth_scopes %}
9661070
'{{ scope }}',
9671071
{%- endfor %}
9681072
),
1073+
ssl_credentials=mock_ssl_cred,
9691074
)
9701075
assert transport.grpc_channel == mock_grpc_channel
9711076

@@ -997,12 +1102,13 @@ def test_{{ service.name|snake_case }}_grpc_asyncio_transport_channel_mtls_with_
9971102
grpc_create_channel.assert_called_once_with(
9981103
"mtls.squid.clam.whelk:443",
9991104
credentials=mock_cred,
1000-
ssl_credentials=mock_ssl_cred,
1105+
credentials_file=None,
10011106
scopes=(
10021107
{%- for scope in service.oauth_scopes %}
10031108
'{{ scope }}',
10041109
{%- endfor %}
10051110
),
1111+
ssl_credentials=mock_ssl_cred,
10061112
)
10071113
assert transport.grpc_channel == mock_grpc_channel
10081114

@@ -1036,12 +1142,13 @@ def test_{{ service.name|snake_case }}_grpc_transport_channel_mtls_with_adc(
10361142
grpc_create_channel.assert_called_once_with(
10371143
"mtls.squid.clam.whelk:443",
10381144
credentials=mock_cred,
1039-
ssl_credentials=mock_ssl_cred,
1145+
credentials_file=None,
10401146
scopes=(
10411147
{%- for scope in service.oauth_scopes %}
10421148
'{{ scope }}',
10431149
{%- endfor %}
10441150
),
1151+
ssl_credentials=mock_ssl_cred,
10451152
)
10461153
assert transport.grpc_channel == mock_grpc_channel
10471154

@@ -1075,12 +1182,13 @@ def test_{{ service.name|snake_case }}_grpc_asyncio_transport_channel_mtls_with_
10751182
grpc_create_channel.assert_called_once_with(
10761183
"mtls.squid.clam.whelk:443",
10771184
credentials=mock_cred,
1078-
ssl_credentials=mock_ssl_cred,
1185+
credentials_file=None,
10791186
scopes=(
10801187
{%- for scope in service.oauth_scopes %}
10811188
'{{ scope }}',
10821189
{%- endfor %}
10831190
),
1191+
ssl_credentials=mock_ssl_cred,
10841192
)
10851193
assert transport.grpc_channel == mock_grpc_channel
10861194

0 commit comments

Comments
 (0)