From 426498c95f719b01a905f42ac4b9e7726d4e2cdb Mon Sep 17 00:00:00 2001 From: "sentry-autofix[bot]" <157164994+sentry-autofix[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:51:00 +0000 Subject: [PATCH 1/2] Add tests for TokenlessAzureHandler timestamp handling --- upload/tests/test_tokenless_azure.py | 85 ++++++++++++++++++++++++++++ upload/tokenless/azure.py | 5 ++ 2 files changed, 90 insertions(+) create mode 100644 upload/tests/test_tokenless_azure.py diff --git a/upload/tests/test_tokenless_azure.py b/upload/tests/test_tokenless_azure.py new file mode 100644 index 0000000000..1bf0d923ec --- /dev/null +++ b/upload/tests/test_tokenless_azure.py @@ -0,0 +1,85 @@ +import pytest +from datetime import datetime, timedelta +from unittest.mock import patch +from rest_framework.exceptions import NotFound + +from upload.tokenless.azure import TokenlessAzureHandler + +@pytest.fixture +def upload_params(): + return { + "job": "899861", + "project": "public", + "server_uri": "https://dev.azure.com/dnceng-public/", + "build": "20241219.14", + "commit": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3" + } + +def test_verify_handles_nanosecond_timestamp(upload_params): + """ + Test that the handler correctly processes timestamps with nanosecond precision + from the Azure DevOps API. + """ + handler = TokenlessAzureHandler(upload_params) + + # Mock a response with nanosecond precision timestamp (7 digits after decimal) + current_time = datetime.now() + timestamp = current_time.strftime("%Y-%m-%dT%H:%M:%S.1234567Z") + + mock_build_response = { + "status": "completed", + "finishTime": timestamp, + "buildNumber": "20241219.14", + "sourceVersion": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", + "repository": {"type": "GitHub"} + } + + with patch.object(handler, 'get_build', return_value=mock_build_response): + service = handler.verify() + assert service == "github" + +def test_verify_handles_microsecond_timestamp(upload_params): + """ + Test that the handler still works correctly with regular microsecond precision + timestamps. + """ + handler = TokenlessAzureHandler(upload_params) + + # Mock a response with microsecond precision (6 digits after decimal) + current_time = datetime.now() + timestamp = current_time.strftime("%Y-%m-%dT%H:%M:%S.123456Z") + + mock_build_response = { + "status": "completed", + "finishTime": timestamp, + "buildNumber": "20241219.14", + "sourceVersion": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", + "repository": {"type": "GitHub"} + } + + with patch.object(handler, 'get_build', return_value=mock_build_response): + service = handler.verify() + assert service == "github" + +def test_verify_rejects_old_timestamp(upload_params): + """ + Test that the handler correctly rejects timestamps older than 4 minutes, + even with nanosecond precision. + """ + handler = TokenlessAzureHandler(upload_params) + + # Create a timestamp that's more than 4 minutes old + old_time = datetime.now() - timedelta(minutes=5) + timestamp = old_time.strftime("%Y-%m-%dT%H:%M:%S.1234567Z") + + mock_build_response = { + "status": "completed", + "finishTime": timestamp, + "buildNumber": "20241219.14", + "sourceVersion": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", + "repository": {"type": "GitHub"} + } + + with patch.object(handler, 'get_build', return_value=mock_build_response): + with pytest.raises(NotFound, match="Azure build has already finished"): + handler.verify() \ No newline at end of file diff --git a/upload/tokenless/azure.py b/upload/tokenless/azure.py index 9e6da9bded..bb8ea39320 100644 --- a/upload/tokenless/azure.py +++ b/upload/tokenless/azure.py @@ -78,6 +78,11 @@ def verify(self): # Build should have finished within the last 4 mins OR should have an 'inProgress' flag if build["status"] == "completed": finishTimestamp = build["finishTime"].replace("T", " ").replace("Z", "") + # Azure DevOps API returns nanosecond precision (7 digits), but Python only supports + # microsecond precision (6 digits). Truncate to 6 digits after decimal. + if "." in finishTimestamp: + base, fraction = finishTimestamp.rsplit(".", 1) + finishTimestamp = f"{base}.{fraction[:6]}" buildFinishDateObj = datetime.strptime( finishTimestamp, "%Y-%m-%d %H:%M:%S.%f" ) From 1f4b054ba5e716bec03a720398bc26c4c79b6c2e Mon Sep 17 00:00:00 2001 From: trent-codecov Date: Fri, 20 Dec 2024 10:06:37 -0500 Subject: [PATCH 2/2] Fix format --- upload/tests/test_tokenless_azure.py | 41 ++++++++++++++++------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/upload/tests/test_tokenless_azure.py b/upload/tests/test_tokenless_azure.py index 1bf0d923ec..536e591f4c 100644 --- a/upload/tests/test_tokenless_azure.py +++ b/upload/tests/test_tokenless_azure.py @@ -1,10 +1,12 @@ -import pytest from datetime import datetime, timedelta from unittest.mock import patch + +import pytest from rest_framework.exceptions import NotFound from upload.tokenless.azure import TokenlessAzureHandler + @pytest.fixture def upload_params(): return { @@ -12,74 +14,77 @@ def upload_params(): "project": "public", "server_uri": "https://dev.azure.com/dnceng-public/", "build": "20241219.14", - "commit": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3" + "commit": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", } + def test_verify_handles_nanosecond_timestamp(upload_params): """ Test that the handler correctly processes timestamps with nanosecond precision from the Azure DevOps API. """ handler = TokenlessAzureHandler(upload_params) - + # Mock a response with nanosecond precision timestamp (7 digits after decimal) current_time = datetime.now() timestamp = current_time.strftime("%Y-%m-%dT%H:%M:%S.1234567Z") - + mock_build_response = { "status": "completed", "finishTime": timestamp, "buildNumber": "20241219.14", "sourceVersion": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", - "repository": {"type": "GitHub"} + "repository": {"type": "GitHub"}, } - - with patch.object(handler, 'get_build', return_value=mock_build_response): + + with patch.object(handler, "get_build", return_value=mock_build_response): service = handler.verify() assert service == "github" + def test_verify_handles_microsecond_timestamp(upload_params): """ Test that the handler still works correctly with regular microsecond precision timestamps. """ handler = TokenlessAzureHandler(upload_params) - + # Mock a response with microsecond precision (6 digits after decimal) current_time = datetime.now() timestamp = current_time.strftime("%Y-%m-%dT%H:%M:%S.123456Z") - + mock_build_response = { "status": "completed", "finishTime": timestamp, "buildNumber": "20241219.14", "sourceVersion": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", - "repository": {"type": "GitHub"} + "repository": {"type": "GitHub"}, } - - with patch.object(handler, 'get_build', return_value=mock_build_response): + + with patch.object(handler, "get_build", return_value=mock_build_response): service = handler.verify() assert service == "github" + def test_verify_rejects_old_timestamp(upload_params): """ Test that the handler correctly rejects timestamps older than 4 minutes, even with nanosecond precision. """ handler = TokenlessAzureHandler(upload_params) - + # Create a timestamp that's more than 4 minutes old old_time = datetime.now() - timedelta(minutes=5) timestamp = old_time.strftime("%Y-%m-%dT%H:%M:%S.1234567Z") - + mock_build_response = { "status": "completed", "finishTime": timestamp, "buildNumber": "20241219.14", "sourceVersion": "0f6e31fec5876be932f9e52f739ce1a2e04f11e3", - "repository": {"type": "GitHub"} + "repository": {"type": "GitHub"}, } - - with patch.object(handler, 'get_build', return_value=mock_build_response): + + with patch.object(handler, "get_build", return_value=mock_build_response): with pytest.raises(NotFound, match="Azure build has already finished"): - handler.verify() \ No newline at end of file + handler.verify()