From 5297b74262b8de4d9303e0e814380c046aaeff96 Mon Sep 17 00:00:00 2001 From: Ania Misiorek Date: Wed, 30 Aug 2023 17:12:23 -0400 Subject: [PATCH 1/4] implementation + testing --- Makefile | 2 +- linode_api4/objects/linode.py | 1 + test/fixtures/account_transfer.json | 14 ++++ test/fixtures/linode_types.json | 114 +++++++++++++++++++++++++--- test/integration/conftest.py | 2 +- test/unit/linode_client_test.py | 16 ++++ test/unit/objects/linode_test.py | 3 + 7 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 test/fixtures/account_transfer.json diff --git a/Makefile b/Makefile index 7636f2192..4cd378b1c 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ release: build twine upload dist/* @PHONEY: install -install: clean +install: clean requirements python3 -m pip install . @PHONEY: requirements diff --git a/linode_api4/objects/linode.py b/linode_api4/objects/linode.py index 3bc402b0d..d928b31b6 100644 --- a/linode_api4/objects/linode.py +++ b/linode_api4/objects/linode.py @@ -232,6 +232,7 @@ class Type(Base): "label": Property(), "network_out": Property(), "price": Property(), + "region_prices": Property(), "addons": Property(), "memory": Property(), "transfer": Property(), diff --git a/test/fixtures/account_transfer.json b/test/fixtures/account_transfer.json new file mode 100644 index 000000000..ce4658a6a --- /dev/null +++ b/test/fixtures/account_transfer.json @@ -0,0 +1,14 @@ +{ + "quota": 471, + "used": 737373, + "billable": 0, + + "region_transfers": [ + { + "id": "ap-west", + "used": 1, + "quota": 5010, + "billable": 0 + } + ] +} \ No newline at end of file diff --git a/test/fixtures/linode_types.json b/test/fixtures/linode_types.json index b270da778..c864082e8 100644 --- a/test/fixtures/linode_types.json +++ b/test/fixtures/linode_types.json @@ -1,8 +1,8 @@ { -"results": 4, -"pages": 1, -"page": 1, -"data": [ + "results": 4, + "pages": 1, + "page": 1, + "data": [ { "disk": 20480, "memory": 1024, @@ -12,7 +12,19 @@ "price": { "hourly": 0.003, "monthly": 2 - } + }, + "region_prices": [ + { + "id": "ap-west", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ] } }, "class": "nanode", @@ -25,6 +37,18 @@ "hourly": 0.0075, "monthly": 5 }, + "region_prices": [ + { + "id": "us-east", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ], "successor": null }, { @@ -36,7 +60,19 @@ "price": { "hourly": 0.008, "monthly": 5 - } + }, + "region_prices": [ + { + "id": "ap-west", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ] } }, "class": "highmem", @@ -49,6 +85,18 @@ "hourly": 0.09, "monthly": 60 }, + "region_prices": [ + { + "id": "us-east", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ], "successor": null }, { @@ -60,7 +108,19 @@ "price": { "hourly": 0.004, "monthly": 2.5 - } + }, + "region_prices": [ + { + "id": "ap-west", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ] } }, "class": "standard", @@ -73,6 +133,18 @@ "hourly": 0.015, "monthly": 10 }, + "region_prices": [ + { + "id": "us-east", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ], "successor": null }, { @@ -84,7 +156,19 @@ "price": { "hourly": 0.008, "monthly": 5 - } + }, + "region_prices": [ + { + "id": "ap-west", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ] } }, "class": "gpu", @@ -97,7 +181,19 @@ "hourly": 0.03, "monthly": 20 }, + "region_prices": [ + { + "id": "us-east", + "hourly": 0.02, + "monthly": 20 + }, + { + "id": "ap-northeast", + "hourly": 0.02, + "monthly": 20 + } + ], "successor": null } ] -} +} \ No newline at end of file diff --git a/test/integration/conftest.py b/test/integration/conftest.py index b3fa15fb2..69248b57a 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -3,7 +3,7 @@ import pytest -from linode_api4.linode_client import LinodeClient, LongviewSubscription +from linode_api4.linode_client import LinodeClient ENV_TOKEN_NAME = "LINODE_TOKEN" RUN_LONG_TESTS = "RUN_LONG_TESTS" diff --git a/test/unit/linode_client_test.py b/test/unit/linode_client_test.py index 512449aa3..acc689bdb 100644 --- a/test/unit/linode_client_test.py +++ b/test/unit/linode_client_test.py @@ -411,6 +411,22 @@ def test_payments(self): self.assertEqual(payment.date, datetime(2015, 1, 1, 5, 1, 2)) self.assertEqual(payment.usd, 1000) + def test_account_transfer(self): + """ + Tests that payments can be retrieved + """ + transfer = self.client.account.transfer() + + self.assertEqual(transfer.quota, 471) + self.assertEqual(transfer.used, 737373) + self.assertEqual(transfer.billable, 0) + + self.assertEqual(len(transfer.region_transfers), 1) + self.assertEqual(transfer.region_transfers[0].id, "ap-west") + self.assertEqual(transfer.region_transfers[0].used, 1) + self.assertEqual(transfer.region_transfers[0].quota, 5010) + self.assertEqual(transfer.region_transfers[0].billable, 0) + class BetaProgramGroupTest(ClientBaseCase): """ diff --git a/test/unit/objects/linode_test.py b/test/unit/objects/linode_test.py index 1f75f8c1a..2aa280fef 100644 --- a/test/unit/objects/linode_test.py +++ b/test/unit/objects/linode_test.py @@ -533,6 +533,8 @@ def test_get_types(self): self.assertIsNotNone(t.type_class) self.assertIsNotNone(t.gpus) self.assertIsNone(t.successor) + self.assertIsNotNone(t.region_prices) + self.assertIsNotNone(t.addons.backups.region_prices) def test_get_type_by_id(self): """ @@ -546,6 +548,7 @@ def test_get_type_by_id(self): self.assertEqual(t.label, "Linode 1024") self.assertEqual(t.disk, 20480) self.assertEqual(t.type_class, "nanode") + self.assertEqual(t.region_prices[0].id, "us-east") def test_get_type_gpu(self): """ From 98511ad2326185944ca35376a8f048138779ac92 Mon Sep 17 00:00:00 2001 From: Ania Misiorek Date: Wed, 30 Aug 2023 17:16:27 -0400 Subject: [PATCH 2/4] linting --- test/unit/linode_client_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/linode_client_test.py b/test/unit/linode_client_test.py index b47c64ade..24a6d8ac3 100644 --- a/test/unit/linode_client_test.py +++ b/test/unit/linode_client_test.py @@ -470,7 +470,7 @@ def test_account_transfer(self): self.assertEqual(transfer.region_transfers[0].quota, 5010) self.assertEqual(transfer.region_transfers[0].billable, 0) - + class BetaProgramGroupTest(ClientBaseCase): """ Tests methods of the BetaProgramGroup From 9915f6de9ac606c5e65c4e4c8b3944f9c3f6674f Mon Sep 17 00:00:00 2001 From: Youjung Kim <126618609+ykim-1@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:32:22 -0700 Subject: [PATCH 3/4] test: upload test report to account's object storage (#326) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Description Storing test executions from dev/main branch to test account's object storage ## 📷 Preview **If applicable, include a screenshot or code snippet of this change. Otherwise, please remove this section.** --- .github/workflows/e2e-test-pr.yml | 30 +++++++++++++++-- test/integration/conftest.py | 2 +- test/script/add_to_xml_test_report.py | 41 ++++++++++++++++++++++ test/script/test_report_upload_script.py | 43 ++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 test/script/add_to_xml_test_report.py create mode 100644 test/script/test_report_upload_script.py diff --git a/.github/workflows/e2e-test-pr.yml b/.github/workflows/e2e-test-pr.yml index af7e1bcaf..00391cf19 100644 --- a/.github/workflows/e2e-test-pr.yml +++ b/.github/workflows/e2e-test-pr.yml @@ -78,11 +78,37 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: make INTEGRATION_TEST_PATH="${{ inputs.test_path }}" testint - if: ${{ steps.validate-tests.outputs.match == '' }} + - name: Run Integration tests + run: | + timestamp=$(date +'%Y%m%d%H%M') + report_filename="${timestamp}_sdk_test_report.xml" + status=0 + if ! python3 -m pytest test/integration/${INTEGRATION_TEST_PATH} --junitxml="${report_filename}"; then + echo "Tests failed, but attempting to upload results anyway" + fi env: LINODE_TOKEN: ${{ secrets.LINODE_TOKEN }} + - name: Set release version env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Add additional information to XML report + run: | + filename=$(ls | grep -E '^[0-9]{12}_sdk_test_report\.xml$') + python test/script/add_to_xml_test_report.py \ + --branch_name "${{ env.RELEASE_VERSION }}" \ + --gha_run_id "$GITHUB_RUN_ID" \ + --gha_run_number "$GITHUB_RUN_NUMBER" \ + --xmlfile "${filename}" + + - name: Upload test results + run: | + report_filename=$(ls | grep -E '^[0-9]{12}_sdk_test_report\.xml$') + python3 test/script/test_report_upload_script.py "${report_filename}" + env: + LINODE_CLI_OBJ_ACCESS_KEY: ${{ secrets.LINODE_CLI_OBJ_ACCESS_KEY }} + LINODE_CLI_OBJ_SECRET_KEY: ${{ secrets.LINODE_CLI_OBJ_SECRET_KEY }} + - uses: actions/github-script@v6 id: update-check-run if: ${{ inputs.pull_request_number != '' && fromJson(steps.commit-hash.outputs.data).repository.pullRequest.headRef.target.oid == inputs.sha }} diff --git a/test/integration/conftest.py b/test/integration/conftest.py index b3fa15fb2..69248b57a 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -3,7 +3,7 @@ import pytest -from linode_api4.linode_client import LinodeClient, LongviewSubscription +from linode_api4.linode_client import LinodeClient ENV_TOKEN_NAME = "LINODE_TOKEN" RUN_LONG_TESTS = "RUN_LONG_TESTS" diff --git a/test/script/add_to_xml_test_report.py b/test/script/add_to_xml_test_report.py new file mode 100644 index 000000000..d486028be --- /dev/null +++ b/test/script/add_to_xml_test_report.py @@ -0,0 +1,41 @@ +import argparse +import xml.etree.ElementTree as ET + +# Parse command-line arguments +parser = argparse.ArgumentParser( + description="Modify XML with workflow information" +) +parser.add_argument("--branch_name", required=True) +parser.add_argument("--gha_run_id", required=True) +parser.add_argument("--gha_run_number", required=True) +parser.add_argument( + "--xmlfile", required=True +) # Added argument for XML file path + +args = parser.parse_args() + +# Open and parse the XML file +xml_file_path = args.xmlfile +tree = ET.parse(xml_file_path) +root = tree.getroot() + +# Create new elements for the information +branch_name_element = ET.Element("branch_name") +branch_name_element.text = args.branch_name + +gha_run_id_element = ET.Element("gha_run_id") +gha_run_id_element.text = args.gha_run_id + +gha_run_number_element = ET.Element("gha_run_number") +gha_run_number_element.text = args.gha_run_number + +# Add the new elements to the root of the XML +root.append(branch_name_element) +root.append(gha_run_id_element) +root.append(gha_run_number_element) + +# Save the modified XML +modified_xml_file_path = xml_file_path # Overwrite it +tree.write(modified_xml_file_path) + +print(f"Modified XML saved to {modified_xml_file_path}") diff --git a/test/script/test_report_upload_script.py b/test/script/test_report_upload_script.py new file mode 100644 index 000000000..5dd1a9e31 --- /dev/null +++ b/test/script/test_report_upload_script.py @@ -0,0 +1,43 @@ +import os +import sys + +import boto3 +from botocore.exceptions import NoCredentialsError + +ACCESS_KEY = os.environ.get("LINODE_CLI_OBJ_ACCESS_KEY") +SECRET_KEY = os.environ.get("LINODE_CLI_OBJ_SECRET_KEY") +BUCKET_NAME = "dx-test-results" + +linode_obj_config = { + "aws_access_key_id": ACCESS_KEY, + "aws_secret_access_key": SECRET_KEY, + "endpoint_url": "https://us-southeast-1.linodeobjects.com", +} + + +def upload_to_linode_object_storage(file_name): + try: + s3 = boto3.client("s3", **linode_obj_config) + + s3.upload_file(Filename=file_name, Bucket=BUCKET_NAME, Key=file_name) + + print(f"Successfully uploaded {file_name} to Linode Object Storage.") + + except NoCredentialsError: + print( + "Credentials not available. Ensure you have set your AWS credentials." + ) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python upload_to_linode.py ") + sys.exit(1) + + file_name = sys.argv[1] + + if not file_name: + print("Error: The provided file name is empty or invalid.") + sys.exit(1) + + upload_to_linode_object_storage(file_name) From 182dec6d9ff78a182610cbddd8ac34e58ddd0c93 Mon Sep 17 00:00:00 2001 From: Lena Garber Date: Mon, 25 Sep 2023 10:51:22 -0400 Subject: [PATCH 4/4] Add integration test --- test/integration/conftest.py | 19 ++++++++++++++++++- test/integration/models/test_linode.py | 16 ++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/test/integration/conftest.py b/test/integration/conftest.py index 69248b57a..a17a6f847 100644 --- a/test/integration/conftest.py +++ b/test/integration/conftest.py @@ -6,6 +6,8 @@ from linode_api4.linode_client import LinodeClient ENV_TOKEN_NAME = "LINODE_TOKEN" +ENV_API_URL_NAME = "LINODE_API_URL" +ENV_API_CA_NAME = "LINODE_API_CA" RUN_LONG_TESTS = "RUN_LONG_TESTS" @@ -13,6 +15,15 @@ def get_token(): return os.environ.get(ENV_TOKEN_NAME, None) +def get_api_url(): + return os.environ.get(ENV_API_URL_NAME, "https://api.linode.com/v4beta") + + +def get_api_ca_file(): + result = os.environ.get(ENV_API_CA_NAME, None) + return result if result != "" else None + + def run_long_tests(): return os.environ.get(RUN_LONG_TESTS, None) @@ -71,7 +82,13 @@ def ssh_key_gen(): @pytest.fixture(scope="session") def get_client(): token = get_token() - client = LinodeClient(token) + api_url = get_api_url() + api_ca_file = get_api_ca_file() + client = LinodeClient( + token, + base_url=api_url, + ca_path=api_ca_file, + ) return client diff --git a/test/integration/models/test_linode.py b/test/integration/models/test_linode.py index 4427c7d5d..22f5709c7 100644 --- a/test/integration/models/test_linode.py +++ b/test/integration/models/test_linode.py @@ -398,6 +398,22 @@ def test_get_linode_types(get_client): assert "g6-nanode-1" in ids +def test_get_linode_types_overrides(get_client): + types = get_client.linode.types() + + target_types = [ + v + for v in types + if len(v.region_prices) > 0 and v.region_prices[0].hourly > 0 + ] + + assert len(target_types) > 0 + + for linode_type in target_types: + assert linode_type.region_prices[0].hourly >= 0 + assert linode_type.region_prices[0].monthly >= 0 + + def test_get_linode_type_by_id(get_client): pytest.skip( "Might need Type to match how other object models are behaving e.g. client.load(Type, 123)"