Skip to content

Commit 18bc377

Browse files
chore: use better fake for credentials in tests (#996)
1 parent 6b762df commit 18bc377

File tree

5 files changed

+57
-80
lines changed

5 files changed

+57
-80
lines changed

google/cloud/sql/connector/refresh_utils.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,11 @@ async def _get_metadata(
7979
:raises TypeError: If any of the arguments are not the specified type.
8080
"""
8181

82-
if not isinstance(credentials, Credentials):
83-
raise TypeError(
84-
"credentials must be of type google.auth.credentials.Credentials,"
85-
f" got {type(credentials)}"
86-
)
87-
elif not isinstance(project, str):
82+
if not isinstance(project, str):
8883
raise TypeError(f"project must be of type str, got {type(project)}")
89-
elif not isinstance(region, str):
84+
if not isinstance(region, str):
9085
raise TypeError(f"region must be of type str, got {type(region)}")
91-
elif not isinstance(instance, str):
86+
if not isinstance(instance, str):
9287
raise TypeError(f"instance must be of type str, got {type(instance)}")
9388

9489
if not credentials.valid:
@@ -172,16 +167,11 @@ async def _get_ephemeral(
172167

173168
logger.debug(f"['{instance}']: Requesting ephemeral certificate")
174169

175-
if not isinstance(credentials, Credentials):
176-
raise TypeError(
177-
"credentials must be of type google.auth.credentials.Credentials,"
178-
f" got {type(credentials)}"
179-
)
180-
elif not isinstance(project, str):
170+
if not isinstance(project, str):
181171
raise TypeError(f"project must be of type str, got {type(project)}")
182-
elif not isinstance(instance, str):
172+
if not isinstance(instance, str):
183173
raise TypeError(f"instance must be of type str, got {type(instance)}")
184-
elif not isinstance(pub_key, str):
174+
if not isinstance(pub_key, str):
185175
raise TypeError(f"pub_key must be of type str, got {type(pub_key)}")
186176

187177
if not credentials.valid:

tests/conftest.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@
2121

2222
from aioresponses import aioresponses
2323
from google.auth.credentials import Credentials
24-
from google.auth.credentials import with_scopes_if_required
25-
from google.oauth2 import service_account
2624
import pytest # noqa F401 Needed to run the tests
25+
from unit.mocks import FakeCredentials # type: ignore
2726
from unit.mocks import FakeCSQLInstance # type: ignore
2827

2928
from google.cloud.sql.connector import Connector
@@ -75,27 +74,8 @@ def connect_string() -> str:
7574

7675

7776
@pytest.fixture
78-
def fake_credentials() -> Credentials:
79-
fake_service_account = {
80-
"type": "service_account",
81-
"project_id": "a-project-id",
82-
"private_key_id": "a-private-key-id",
83-
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDY3E8o1NEFcjMM\nHW/5ZfFJw29/8NEqpViNjQIx95Xx5KDtJ+nWn9+OW0uqsSqKlKGhAdAo+Q6bjx2c\nuXVsXTu7XrZUY5Kltvj94DvUa1wjNXs606r/RxWTJ58bfdC+gLLxBfGnB6CwK0YQ\nxnfpjNbkUfVVzO0MQD7UP0Hl5ZcY0Puvxd/yHuONQn/rIAieTHH1pqgW+zrH/y3c\n59IGThC9PPtugI9ea8RSnVj3PWz1bX2UkCDpy9IRh9LzJLaYYX9RUd7++dULUlat\nAaXBh1U6emUDzhrIsgApjDVtimOPbmQWmX1S60mqQikRpVYZ8u+NDD+LNw+/Eovn\nxCj2Y3z1AgMBAAECggEAWDBzoqO1IvVXjBA2lqId10T6hXmN3j1ifyH+aAqK+FVl\nGjyWjDj0xWQcJ9ync7bQ6fSeTeNGzP0M6kzDU1+w6FgyZqwdmXWI2VmEizRjwk+/\n/uLQUcL7I55Dxn7KUoZs/rZPmQDxmGLoue60Gg6z3yLzVcKiDc7cnhzhdBgDc8vd\nQorNAlqGPRnm3EqKQ6VQp6fyQmCAxrr45kspRXNLddat3AMsuqImDkqGKBmF3Q1y\nxWGe81LphUiRqvqbyUlh6cdSZ8pLBpc9m0c3qWPKs9paqBIvgUPlvOZMqec6x4S6\nChbdkkTRLnbsRr0Yg/nDeEPlkhRBhasXpxpMUBgPywKBgQDs2axNkFjbU94uXvd5\nznUhDVxPFBuxyUHtsJNqW4p/ujLNimGet5E/YthCnQeC2P3Ym7c3fiz68amM6hiA\nOnW7HYPZ+jKFnefpAtjyOOs46AkftEg07T9XjwWNPt8+8l0DYawPoJgbM5iE0L2O\nx8TU1Vs4mXc+ql9F90GzI0x3VwKBgQDqZOOqWw3hTnNT07Ixqnmd3dugV9S7eW6o\nU9OoUgJB4rYTpG+yFqNqbRT8bkx37iKBMEReppqonOqGm4wtuRR6LSLlgcIU9Iwx\nyfH12UWqVmFSHsgZFqM/cK3wGev38h1WBIOx3/djKn7BdlKVh8kWyx6uC8bmV+E6\nOoK0vJD6kwKBgHAySOnROBZlqzkiKW8c+uU2VATtzJSydrWm0J4wUPJifNBa/hVW\ndcqmAzXC9xznt5AVa3wxHBOfyKaE+ig8CSsjNyNZ3vbmr0X04FoV1m91k2TeXNod\njMTobkPThaNm4eLJMN2SQJuaHGTGERWC0l3T18t+/zrDMDCPiSLX1NAvAoGBAN1T\nVLJYdjvIMxf1bm59VYcepbK7HLHFkRq6xMJMZbtG0ryraZjUzYvB4q4VjHk2UDiC\nlhx13tXWDZH7MJtABzjyg+AI7XWSEQs2cBXACos0M4Myc6lU+eL+iA+OuoUOhmrh\nqmT8YYGu76/IBWUSqWuvcpHPpwl7871i4Ga/I3qnAoGBANNkKAcMoeAbJQK7a/Rn\nwPEJB+dPgNDIaboAsh1nZhVhN5cvdvCWuEYgOGCPQLYQF0zmTLcM+sVxOYgfy8mV\nfbNgPgsP5xmu6dw2COBKdtozw0HrWSRjACd1N4yGu75+wPCcX/gQarcjRcXXZeEa\nNtBLSfcqPULqD+h7br9lEJio\n-----END PRIVATE KEY-----\n",
84-
"client_email": "email@example.com",
85-
"client_id": "12345",
86-
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
87-
"token_uri": "https://oauth2.googleapis.com/token",
88-
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
89-
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/email%40example.com",
90-
}
91-
92-
fake_credentials = service_account.Credentials.from_service_account_info(
93-
fake_service_account
94-
)
95-
fake_credentials = with_scopes_if_required(fake_credentials, scopes=SCOPES)
96-
# stub refresh of credentials
97-
setattr(fake_credentials, "refresh", lambda *args: None)
98-
return fake_credentials
77+
def fake_credentials() -> FakeCredentials:
78+
return FakeCredentials()
9979

10080

10181
def mock_server(server_sock: socket.socket) -> None:

tests/unit/mocks.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,61 @@
1919
import json
2020
import ssl
2121
from tempfile import TemporaryDirectory
22-
from typing import Any, Dict, Optional, Tuple
22+
from typing import Any, Callable, Dict, Optional, Tuple
2323

2424
from cryptography import x509
2525
from cryptography.hazmat.backends import default_backend
2626
from cryptography.hazmat.primitives import hashes
2727
from cryptography.hazmat.primitives import serialization
2828
from cryptography.hazmat.primitives.asymmetric import rsa
2929
from cryptography.x509.oid import NameOID
30+
from google.auth.credentials import Credentials
3031

3132
from google.cloud.sql.connector import IPTypes
3233
from google.cloud.sql.connector.instance import ConnectionInfo
3334
from google.cloud.sql.connector.utils import generate_keys
3435
from google.cloud.sql.connector.utils import write_to_file
3536

3637

38+
class FakeCredentials:
39+
def __init__(
40+
self, token: Optional[str] = None, expiry: Optional[datetime.datetime] = None
41+
) -> None:
42+
self.token = token
43+
self.expiry = expiry
44+
45+
@property
46+
def __class__(self) -> Credentials:
47+
# set class type to google auth Credentials
48+
return Credentials
49+
50+
def refresh(self, request: Callable) -> None:
51+
"""Refreshes the access token."""
52+
self.token = "12345"
53+
self.expiry = datetime.datetime.utcnow() + datetime.timedelta(minutes=60)
54+
55+
@property
56+
def expired(self) -> bool:
57+
"""Checks if the credentials are expired.
58+
59+
Note that credentials can be invalid but not expired because
60+
Credentials with expiry set to None are considered to never
61+
expire.
62+
"""
63+
if self.expiry is None:
64+
return False
65+
return False if self.expiry > datetime.datetime.utcnow() else True
66+
67+
@property
68+
def valid(self) -> bool:
69+
"""Checks the validity of the credentials.
70+
71+
This is True if the credentials have a token and the token
72+
is not expired.
73+
"""
74+
return self.token is not None and not self.expired
75+
76+
3777
class MockInstance:
3878
_enable_iam_auth: bool
3979

@@ -238,6 +278,10 @@ def generate_ephemeral(self, client_bytes: str) -> str:
238278
client_bytes.encode("UTF-8"), default_backend()
239279
) # type: ignore
240280
ephemeral_cert = client_key_signed_cert(self.cert, self.key, client_key)
281+
print(
282+
"Test generate time: ",
283+
datetime.datetime.utcnow() + datetime.timedelta(minutes=10),
284+
)
241285
return json.dumps(
242286
{
243287
"ephemeralCert": {

tests/unit/test_instance.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ async def test_perform_refresh(
264264

265265
@pytest.mark.asyncio
266266
async def test_perform_refresh_expiration(
267-
instance: Instance, fake_credentials: Credentials
267+
instance: Instance,
268268
) -> None:
269269
"""
270270
Test that _perform_refresh returns ConnectionInfo with proper expiration.
@@ -274,15 +274,14 @@ async def test_perform_refresh_expiration(
274274
"""
275275
# set credentials expiration to 1 minute from now
276276
expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=1)
277+
credentials = mocks.FakeCredentials(token="my-token", expiry=expiration)
277278
setattr(instance, "_enable_iam_auth", True)
278279
# set downscoped credential to mock
279280
with patch(
280281
"google.cloud.sql.connector.refresh_utils._downscope_credentials"
281282
) as mock_auth:
282-
setattr(fake_credentials, "expiry", expiration)
283-
mock_auth.return_value = fake_credentials
283+
mock_auth.return_value = credentials
284284
instance_metadata = await instance._perform_refresh()
285-
286285
# verify instance metadata object is returned
287286
assert isinstance(instance_metadata, ConnectionInfo)
288287
# verify instance metadata uses credentials expiration

tests/unit/test_refresh_utils.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,6 @@ async def test_get_ephemeral_TypeError(credentials: Credentials) -> None:
9797
instance = "my-instance"
9898
pub_key = "key"
9999

100-
# incorrect credentials type
101-
with pytest.raises(TypeError):
102-
await _get_ephemeral(
103-
client_session=client_session,
104-
sqladmin_api_endpoint="https://sqladmin.googleapis.com",
105-
credentials="bad-credentials",
106-
project=project,
107-
instance=instance,
108-
pub_key=pub_key,
109-
)
110100
# incorrect project type
111101
with pytest.raises(TypeError):
112102
await _get_ephemeral(
@@ -188,16 +178,6 @@ async def test_get_metadata_TypeError(credentials: Credentials) -> None:
188178
region = "my-region"
189179
instance = "my-instance"
190180

191-
# incorrect credentials type
192-
with pytest.raises(TypeError):
193-
await _get_metadata(
194-
client_session=client_session,
195-
sqladmin_api_endpoint="https://sqladmin.googleapis.com",
196-
credentials="bad-credentials",
197-
project=project,
198-
region=region,
199-
instance=instance,
200-
)
201181
# incorrect project type
202182
with pytest.raises(TypeError):
203183
await _get_metadata(
@@ -287,22 +267,6 @@ async def test_is_valid_with_expired_metadata() -> None:
287267
assert not await _is_valid(task)
288268

289269

290-
# TODO: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector/issues/901
291-
# def test_downscope_credentials_service_account(fake_credentials: Credentials) -> None:
292-
# """
293-
# Test _downscope_credentials with google.oauth2.service_account.Credentials
294-
# which mimics an authenticated service account.
295-
# """
296-
# # override actual refresh URI
297-
# setattr(fake_credentials, "with_scopes", google.auth.credentials.Credentials(scopes=["https://www.googleapis.com/auth/sqlservice.login"]))
298-
# credentials = _downscope_credentials(fake_credentials)
299-
# # verify default credential scopes have not been altered
300-
# assert fake_credentials.scopes == SCOPES
301-
# # verify downscoped credentials have new scope
302-
# assert credentials.scopes == ["https://www.googleapis.com/auth/sqlservice.login"]
303-
# assert credentials != fake_credentials
304-
305-
306270
def test_downscope_credentials_user() -> None:
307271
"""
308272
Test _downscope_credentials with google.oauth2.credentials.Credentials

0 commit comments

Comments
 (0)