Skip to content

Commit 712b0a0

Browse files
feat(examples): Add test suite for examples/utils and ensure Ads API v19 compatibility
This commit introduces a test suite for the utility modules located in the `examples/utils/` directory. Key changes: - Created a new test directory: `examples/utils/tests/`. - Added `examples/utils/tests/__init__.py`. - Added `examples/utils/tests/test_example_helpers.py` with unit tests for `get_printable_datetime` and `get_image_bytes_from_url` using the `unittest` framework and `unittest.mock`. - Verified that the existing `google-ads` client library (v26.1.0) already supports Google Ads API v19, so no direct dependency changes for API versioning were required. - Updated `noxfile.py`: Modified the `TEST_COMMAND` for `unittest discover` to scan from the project root (`-s .`) instead of just the top-level `tests` directory. This ensures the new tests in `examples/utils/tests/` are discovered and executed by the standard `nox` testing sessions. - Adjusted the import statement in `test_example_helpers.py` to `from examples.utils import example_helpers` to ensure correct module resolution when tests are run from the project root.
1 parent 4f350a8 commit 712b0a0

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

examples/utils/tests/__init__.py

Whitespace-only changes.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright 2024 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+
# https://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+
import unittest
16+
from unittest.mock import patch, MagicMock
17+
from datetime import datetime, timezone, timedelta
18+
import re
19+
import requests
20+
21+
# Assuming example_helpers.py is one level up and in the same package path
22+
# This might need adjustment based on how tests are run.
23+
# If running with pytest from the root, this relative import should work.
24+
from examples.utils import example_helpers
25+
26+
27+
class TestExampleHelpers(unittest.TestCase):
28+
29+
def test_get_printable_datetime(self):
30+
dt_str = example_helpers.get_printable_datetime()
31+
self.assertIsInstance(dt_str, str)
32+
33+
# Regex to check the ISO format with milliseconds and timezone
34+
# Example: 2023-03-23T15:45:25.123+04:00 or 2023-03-23T15:45:25.123Z
35+
iso_pattern = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}(Z|[+-]\d{2}:\d{2})$"
36+
self.assertIsNotNone(re.fullmatch(iso_pattern, dt_str),
37+
f"Datetime string '{dt_str}' does not match ISO pattern.")
38+
39+
# Check if the time is current (within a small delta, e.g., 5 seconds)
40+
# This part can be a bit flaky in CI, but useful locally.
41+
try:
42+
# Parse the string. Python's fromisoformat doesn't always handle all timezone offsets perfectly,
43+
# especially if they are not 'Z'. We might need to manually adjust or use a library
44+
# if this becomes an issue. For now, let's try direct parsing if possible,
45+
# or compare with a freshly generated one.
46+
parsed_dt = datetime.fromisoformat(dt_str)
47+
now_utc = datetime.now(timezone.utc)
48+
# Ensure both are offset-aware for comparison
49+
if parsed_dt.tzinfo is None: # Should not happen with the function's logic
50+
parsed_dt = parsed_dt.replace(tzinfo=timezone.utc)
51+
52+
# Get current time in the same timezone as parsed_dt for accurate comparison
53+
now_in_parsed_dt_tz = datetime.now(parsed_dt.tzinfo)
54+
55+
self.assertLess(abs(parsed_dt - now_in_parsed_dt_tz), timedelta(seconds=5),
56+
"The generated datetime is not close to the current time.")
57+
except ValueError as e:
58+
self.fail(f"Could not parse the datetime string '{dt_str}': {e}")
59+
60+
61+
@patch('requests.get')
62+
def test_get_image_bytes_from_url_success(self, mock_requests_get):
63+
mock_response = MagicMock()
64+
expected_content = b"image_bytes_here"
65+
mock_response.content = expected_content
66+
# Make the mock response an iterable to satisfy requests.Response behavior if needed by other code
67+
mock_response.iter_content = MagicMock(return_value=iter([expected_content]))
68+
mock_response.status_code = 200 # success code
69+
mock_requests_get.return_value = mock_response
70+
71+
test_url = "http://example.com/image.png"
72+
actual_content = example_helpers.get_image_bytes_from_url(test_url)
73+
74+
mock_requests_get.assert_called_once_with(test_url)
75+
self.assertEqual(actual_content, expected_content)
76+
77+
@patch('requests.get')
78+
def test_get_image_bytes_from_url_failure(self, mock_requests_get):
79+
test_url = "http://example.com/nonexistent_image.png"
80+
# Configure the mock to raise an exception when .content is accessed or when called
81+
mock_requests_get.side_effect = requests.exceptions.RequestException("Test error")
82+
83+
with self.assertRaises(requests.exceptions.RequestException):
84+
example_helpers.get_image_bytes_from_url(test_url)
85+
86+
mock_requests_get.assert_called_once_with(test_url)
87+
88+
if __name__ == "__main__":
89+
unittest.main()

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"unittest",
2929
"discover",
3030
"--buffer",
31-
"-s=tests",
31+
"-s .",
3232
"-p",
3333
"*_test.py",
3434
]

0 commit comments

Comments
 (0)