From 9dc63b7b180d33618498849ea7b175734f5477e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mal=C3=A9s?= <85952626+joaomper-TE@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:17:23 +0100 Subject: [PATCH 1/5] Update python.yaml --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 1f19a282..bf520065 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -2,9 +2,9 @@ name: Python CI on: push: - branches: [ "main" ] + branches: [ "master" ] pull_request: - branches: [ "main" ] + branches: [ "master" ] jobs: build: From ec9330149648746dbd1fc0123b50edb11a919488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mal=C3=A9s?= <85952626+joaomper-TE@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:17:39 +0100 Subject: [PATCH 2/5] Update release.yaml --- .github/workflows/release.yaml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8d20ebed..52921c33 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,34 +18,42 @@ jobs: FOLDERS_JSON=$(find . -maxdepth 1 -type d -name "thousandeyes-sdk-*" | cut -c 3- | jq -R -s -c 'split("\n")[:-1]') echo "packages=$FOLDERS_JSON" >> "$GITHUB_OUTPUT" deployment: - if: github.event.pull_request.merged == true + #if: github.event.pull_request.merged == true needs: set-package-matrix strategy: + fail-fast: false matrix: - package-name: ${{ fromJSON(needs.set-package-matrix.outputs.packages) }} + package-name: [thousandeyes-sdk-administrative, thousandeyes-sdk-instant-tests, thousandeyes-sdk-usage, thousandeyes-sdk-core] runs-on: ubuntu-latest permissions: id-token: write environment: - name: release - url: https://pypi.org/p/${{ matrix.package-name }} + name: test + url: https://test.pypi.org/p/${{ matrix.package-name }} steps: - uses: actions/checkout@v4 + with: + ref: main + python-version: '3.11' + token: ${{ secrets.TEST_PAT }} - name: Set up Python uses: actions/setup-python@v5 with: cache: pip + python-version: '3.11' cache-dependency-path: '**/pyproject.toml' - name: Install dependencies run: | pip install setuptools wheel build - name: Build run: | - echo $GITHUB_REF_NAME >> ${{ matrix.package-name }}/.version - cp LICENSE NOTICE ${{ matrix.package-name }}/ + echo "$GITHUB_REF_NAME" >> ${{ matrix.package-name }}/.version + + ls ${{ matrix.package-name }}/ + cp LICENSE NOTICE ${{ matrix.package-name }}/ python -m build ${{ matrix.package-name }} --outdir dist/ - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 with: + repository-url: https://test.pypi.org/legacy/ skip-existing: true - From a894ba3d9ddb0f7918a8b4ef0e94aac304af5295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Male=CC=81s?= Date: Fri, 20 Sep 2024 15:59:33 +0100 Subject: [PATCH 3/5] CP-2386 Add tests to Core. --- .github/workflows/python.yaml | 2 +- .../src/thousandeyes_sdk/core/api_client.py | 2 + thousandeyes-sdk-core/test/test_api_client.py | 124 ++++++++++++++++++ .../test/test_api_response.py | 48 +++++++ .../test/test_thousandeyes_retry.py | 43 ++++++ 5 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 thousandeyes-sdk-core/test/test_api_client.py create mode 100644 thousandeyes-sdk-core/test/test_api_response.py create mode 100644 thousandeyes-sdk-core/test/test_thousandeyes_retry.py diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index bf520065..4529c02a 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -23,7 +23,7 @@ jobs: - name: Install and test modules run: | pip install pytest - for module in $(find . -maxdepth 1 -type d -name "thousandeyes-sdk-*" ! -name "thousandeyes-sdk-core" | cut -c 3-); do + for module in $(find . -maxdepth 1 -type d -name "thousandeyes-sdk-*" | cut -c 3-); do pip install -e $module pytest $module done diff --git a/thousandeyes-sdk-core/src/thousandeyes_sdk/core/api_client.py b/thousandeyes-sdk-core/src/thousandeyes_sdk/core/api_client.py index d8305ed4..16aa9859 100644 --- a/thousandeyes-sdk-core/src/thousandeyes_sdk/core/api_client.py +++ b/thousandeyes-sdk-core/src/thousandeyes_sdk/core/api_client.py @@ -428,6 +428,8 @@ class ApiClient: # convert str to class if klass in self.NATIVE_TYPES_MAPPING: klass = self.NATIVE_TYPES_MAPPING[klass] + elif klass == 'dict': + return data else: klass = getattr(models, klass) diff --git a/thousandeyes-sdk-core/test/test_api_client.py b/thousandeyes-sdk-core/test/test_api_client.py new file mode 100644 index 00000000..2cef9ff4 --- /dev/null +++ b/thousandeyes-sdk-core/test/test_api_client.py @@ -0,0 +1,124 @@ +import pytest +import datetime +from unittest.mock import Mock, patch +from thousandeyes_sdk.core.api_client import ApiClient +from thousandeyes_sdk.core.configuration import Configuration +from thousandeyes_sdk.core import rest +from thousandeyes_sdk.core.exceptions import ApiException + + +@pytest.fixture +def api_client(): + config = Configuration() + return ApiClient(configuration=config) + + +def test_api_client_initialization(api_client): + assert api_client.configuration is not None + assert isinstance(api_client.rest_client, rest.RESTClientObject) + assert api_client.default_headers == {} + assert api_client.cookie is None + + +def test_set_default_header(api_client): + api_client.set_default_header('X-Test-Header', 'test_value') + assert api_client.default_headers['X-Test-Header'] == 'test_value' + + +def test_user_agent_property(api_client): + api_client.user_agent = 'test-agent' + assert api_client.user_agent == 'test-agent' + + +def test_get_default(): + default_client = ApiClient.get_default() + assert isinstance(default_client, ApiClient) + + +def test_set_default(): + new_default = ApiClient() + ApiClient.set_default(new_default) + assert ApiClient.get_default() == new_default + + +def test_param_serialize(api_client): + method, url, headers, _, __ = api_client.param_serialize( + method='GET', + resource_path='/test/{id}', + path_params={'id': 1}, + query_params={'aid': '12'}, + header_params={'X-Test': 'test_value'} + ) + assert method == 'GET' + print(url + "3") + assert url == api_client.configuration.host + '/test/1?aid=12' + assert headers['X-Test'] == 'test_value' + + +@patch('thousandeyes_sdk.core.rest.RESTClientObject.request') +def test_call_api(mock_request, api_client): + mock_response = Mock() + mock_response.data = b'{"dummyKey": "someValue"}' + mock_response.status = 200 + mock_request.return_value = mock_response + + response = api_client.call_api( + method='GET', + url='/tests', + body=None, + post_params=None, + _request_timeout=None + ) + assert response.data == b'{"dummyKey": "someValue"}' + assert response.status == 200 + + +@patch('thousandeyes_sdk.core.rest.RESTClientObject.request') +def test_call_api_exception(mock_request, api_client): + mock_request.side_effect = ApiException(status=404, reason="Not Found") + with pytest.raises(ApiException): + api_client.call_api( + method='GET', + url='/tests', + body=None, + post_params=None, + _request_timeout=None + ) + + +def test_response_deserialize(api_client): + mock_response = Mock() + mock_response.data = b'{"dummyKey": "someValue"}' + mock_response.status = 200 + mock_response.getheader.return_value = 'application/json' + mock_response.getheaders.return_value = {} + + response = api_client.response_deserialize( + response_data=mock_response, + response_types_map={'200': 'dict'}, + models={} + ) + assert response.data == {"dummyKey": "someValue"} + assert response.status_code == 200 + + +def test_sanitize_for_serialization(api_client): + data = { + 'str': 'value', + 'int': 1, + 'float': 1.1, + 'bool': True, + 'datetime': datetime.datetime(2023, 1, 1), + 'date': datetime.date(2023, 1, 1), + 'list': [1, 2, 3], + 'dict': {'key': 'value'} + } + sanitized = api_client.sanitize_for_serialization(data) + assert sanitized['str'] == 'value' + assert sanitized['int'] == 1 + assert sanitized['float'] == 1.1 + assert sanitized['bool'] is True + assert sanitized['datetime'] == '2023-01-01T00:00:00' + assert sanitized['date'] == '2023-01-01' + assert sanitized['list'] == [1, 2, 3] + assert sanitized['dict'] == {'key': 'value'} diff --git a/thousandeyes-sdk-core/test/test_api_response.py b/thousandeyes-sdk-core/test/test_api_response.py new file mode 100644 index 00000000..d3b74a35 --- /dev/null +++ b/thousandeyes-sdk-core/test/test_api_response.py @@ -0,0 +1,48 @@ +import pytest +from pydantic import ValidationError +from thousandeyes_sdk.core.api_response import ApiResponse + + +def test_api_response_valid(): + response = ApiResponse( + status_code=200, + headers={"Content-Type": "application/json"}, + data={"key": "value"}, + raw_data=b'{"key": "value"}' + ) + assert response.status_code == 200 + assert response.headers == {"Content-Type": "application/json"} + assert response.data == {"key": "value"} + assert response.raw_data == b'{"key": "value"}' + + +def test_api_response_missing_optional_headers(): + response = ApiResponse( + status_code=200, + data={"key": "value"}, + raw_data=b'{"key": "value"}' + ) + assert response.status_code == 200 + assert response.headers is None + assert response.data == {"key": "value"} + assert response.raw_data == b'{"key": "value"}' + + +def test_api_response_invalid_status_code(): + with pytest.raises(ValidationError): + ApiResponse( + status_code="200", # Invalid type + headers={"Content-Type": "application/json"}, + data={"key": "value"}, + raw_data=b'{"key": "value"}' + ) + + +def test_api_response_invalid_raw_data(): + with pytest.raises(ValidationError): + ApiResponse( + status_code=200, + headers={"Content-Type": "application/json"}, + data={"key": "value"}, + raw_data="raw data" # Invalid type + ) diff --git a/thousandeyes-sdk-core/test/test_thousandeyes_retry.py b/thousandeyes-sdk-core/test/test_thousandeyes_retry.py new file mode 100644 index 00000000..944e5dd6 --- /dev/null +++ b/thousandeyes-sdk-core/test/test_thousandeyes_retry.py @@ -0,0 +1,43 @@ +import time +import pytest +from unittest.mock import Mock +from urllib3 import HTTPResponse +from thousandeyes_sdk.core.thousandeyes_retry import ThousandEyesRetry + + +def test_is_retry_on_429(): + retry = ThousandEyesRetry() + assert retry.is_retry("GET", 429) is True + + +def test_is_retry_on_other_status(): + retry = ThousandEyesRetry(status_forcelist=[500]) + assert retry.is_retry("GET", 500) is True + assert retry.is_retry("GET", 404) is False + + +def test_get_retry_after_with_custom_header(): + response = Mock(spec=HTTPResponse) + response.headers = { + "x-organization-rate-limit-reset": str(int(time.time()) + 100)} + retry = ThousandEyesRetry() + assert retry.get_retry_after(response) == pytest.approx(100, rel=1) + + +def test_get_retry_after_with_no_headers(): + response = Mock(spec=HTTPResponse) + response.headers = {} + retry = ThousandEyesRetry() + assert retry.get_retry_after(response) is None + + +def test_parse_reset_header_valid(): + retry = ThousandEyesRetry() + future_time = str(int(time.time()) + 100) + assert retry._parse_reset_header(future_time) == pytest.approx(100, rel=1) + + +def test_parse_reset_header_invalid(): + retry = ThousandEyesRetry() + assert retry._parse_reset_header("invalid") is None + assert retry._parse_reset_header(None) is None From fab89dda1d0979e38ae03be415ba75980666ac4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joa=CC=83o=20Male=CC=81s?= Date: Tue, 24 Sep 2024 14:50:09 +0100 Subject: [PATCH 4/5] Revert "Update release.yaml" This reverts commit ec9330149648746dbd1fc0123b50edb11a919488. --- .github/workflows/release.yaml | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 52921c33..8d20ebed 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,42 +18,34 @@ jobs: FOLDERS_JSON=$(find . -maxdepth 1 -type d -name "thousandeyes-sdk-*" | cut -c 3- | jq -R -s -c 'split("\n")[:-1]') echo "packages=$FOLDERS_JSON" >> "$GITHUB_OUTPUT" deployment: - #if: github.event.pull_request.merged == true + if: github.event.pull_request.merged == true needs: set-package-matrix strategy: - fail-fast: false matrix: - package-name: [thousandeyes-sdk-administrative, thousandeyes-sdk-instant-tests, thousandeyes-sdk-usage, thousandeyes-sdk-core] + package-name: ${{ fromJSON(needs.set-package-matrix.outputs.packages) }} runs-on: ubuntu-latest permissions: id-token: write environment: - name: test - url: https://test.pypi.org/p/${{ matrix.package-name }} + name: release + url: https://pypi.org/p/${{ matrix.package-name }} steps: - uses: actions/checkout@v4 - with: - ref: main - python-version: '3.11' - token: ${{ secrets.TEST_PAT }} - name: Set up Python uses: actions/setup-python@v5 with: cache: pip - python-version: '3.11' cache-dependency-path: '**/pyproject.toml' - name: Install dependencies run: | pip install setuptools wheel build - name: Build run: | - echo "$GITHUB_REF_NAME" >> ${{ matrix.package-name }}/.version - - ls ${{ matrix.package-name }}/ - cp LICENSE NOTICE ${{ matrix.package-name }}/ + echo $GITHUB_REF_NAME >> ${{ matrix.package-name }}/.version + cp LICENSE NOTICE ${{ matrix.package-name }}/ python -m build ${{ matrix.package-name }} --outdir dist/ - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 with: - repository-url: https://test.pypi.org/legacy/ skip-existing: true + From 7f3df84283d1bda51f6824a37a0e4592a76e91f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Mal=C3=A9s?= <85952626+joaomper-TE@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:50:39 +0100 Subject: [PATCH 5/5] Update python.yaml --- .github/workflows/python.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yaml b/.github/workflows/python.yaml index 4529c02a..9a7cef55 100644 --- a/.github/workflows/python.yaml +++ b/.github/workflows/python.yaml @@ -2,9 +2,9 @@ name: Python CI on: push: - branches: [ "master" ] + branches: [ "main" ] pull_request: - branches: [ "master" ] + branches: [ "main" ] jobs: build: