diff --git a/.env.sample b/.env.sample new file mode 100644 index 00000000..6ef8147c --- /dev/null +++ b/.env.sample @@ -0,0 +1,2 @@ +MPT_API_BASE_URL=https://api... +MPT_API_TOKEN=idt:TKN-... diff --git a/.github/workflows/pr-build-merge.yml b/.github/workflows/pr-build-merge.yml index 95a1db4b..f195d33b 100644 --- a/.github/workflows/pr-build-merge.yml +++ b/.github/workflows/pr-build-merge.yml @@ -26,9 +26,19 @@ jobs: - name: "Build test containers" run: docker compose build app_test + - name: "Create environment file" + run: touch .env + - name: "Run validation & test" run: docker compose run --service-ports app_test + - name: "Run E2E test" + run: docker compose run --service-ports -e MPT_API_BASE_URL=$MPT_API_BASE_URL -e MPT_API_TOKEN=$MPT_API_TOKEN e2e + env: + MPT_API_BASE_URL: ${{ secrets.MPT_API_BASE_URL }} + MPT_API_TOKEN: ${{ secrets.MPT_API_TOKEN }} + + - name: "Run SonarCloud Scan" uses: SonarSource/sonarqube-scan-action@master env: diff --git a/docker-compose.yml b/docker-compose.yml index b4217f32..e60f2a94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: tty: true volumes: - .:/mpt_api_client + env_file: + - .env app_test: container_name: mpt_api_client_test @@ -31,6 +33,8 @@ services: tty: true volumes: - .:/mpt_api_client + env_file: + - .env format: container_name: mpt_api_client_format @@ -41,3 +45,15 @@ services: command: bash -c "ruff check . --select I --fix && ruff format ." volumes: - .:/mpt_api_client + + e2e: + container_name: mpt_api_client_test + build: + context: . + dockerfile: dev.Dockerfile + working_dir: /mpt_api_client + command: bash -c "pytest -m e2e -p no:randomly --junitxml=e2e-report.xml" + volumes: + - .:/mpt_api_client + env_file: + - .env diff --git a/mpt_api_client/exceptions.py b/mpt_api_client/exceptions.py index 4a17c1a8..168a5ea9 100644 --- a/mpt_api_client/exceptions.py +++ b/mpt_api_client/exceptions.py @@ -11,27 +11,27 @@ class MPTError(Exception): class MPTHttpError(MPTError): """Represents an HTTP error.""" - def __init__(self, status_code: int, text: str): + def __init__(self, status_code: int, message: str, body: str): self.status_code = status_code - self.text = text - super().__init__(f"{self.status_code} - {self.text}") + self.body = body + super().__init__(f"HTTP {status_code}: {message}") class MPTAPIError(MPTHttpError): """Represents an API error.""" - def __init__(self, status_code: int, payload: dict[str, str]): - super().__init__(status_code, json.dumps(payload)) + def __init__(self, status_code: int, message: str, payload: dict[str, str]): + super().__init__(status_code, message, json.dumps(payload)) self.payload = payload - self.status: str | None = payload.get("status") - self.title: str | None = payload.get("title") - self.detail: str | None = payload.get("detail") + self.status: str | None = payload.get("status") or payload.get("statusCode") + self.title: str | None = payload.get("title") or payload.get("message") + self.detail: str | None = payload.get("detail") or message self.trace_id: str | None = payload.get("traceId") self.errors: str | None = payload.get("errors") @override def __str__(self) -> str: - base = f"{self.status} {self.title} - {self.detail} ({self.trace_id})" + base = f"{self.status} {self.title} - {self.detail} ({self.trace_id or 'no-trace-id'})" # noqa: WPS221 WPS237 if self.errors: return f"{base}\n{json.dumps(self.errors, indent=2)}" @@ -57,11 +57,13 @@ def transform_http_status_exception(http_status_exception: HTTPStatusError) -> M try: return MPTAPIError( status_code=http_status_exception.response.status_code, + message=http_status_exception.args[0], payload=http_status_exception.response.json(), ) except json.JSONDecodeError: - payload = http_status_exception.response.content.decode() + body = http_status_exception.response.content.decode() return MPTHttpError( status_code=http_status_exception.response.status_code, - text=payload, + message=http_status_exception.args[0], + body=body, ) diff --git a/pyproject.toml b/pyproject.toml index 95705ea4..6cff4648 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,10 +37,11 @@ dev = [ "pytest-deadfixtures==2.2.*", "pytest-mock==3.14.*", "pytest-randomly==3.16.*", + "pytest-rerunfailures>=16.1", "pytest-xdist==3.6.*", "responses==0.25.*", "respx==0.22.*", - "ruff==0.12.11", # force ruff version to have same formatting everywhere + "ruff==0.12.11", # force ruff version to have same formatting everywhere "typing-extensions==4.13.*", "wemake-python-styleguide==1.3.*", ] @@ -58,13 +59,17 @@ build-backend = "hatchling.build" [tool.pytest.ini_options] testpaths = "tests" pythonpath = "." -addopts = "--cov=mpt_api_client --cov-report=term-missing --cov-report=html --cov-report=xml --import-mode=importlib" +addopts = "--cov=mpt_api_client --cov-report=term-missing --cov-report=html --cov-report=xml --import-mode=importlib -m 'not e2e'" log_cli = false asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" filterwarnings = [ "ignore:Support for class-based `config` is deprecated:DeprecationWarning", "ignore:pkg_resources is deprecated as an API:DeprecationWarning", ] +markers = [ + "e2e: marks tests as e2e" +] [tool.coverage.run] branch = true diff --git a/setup.cfg b/setup.cfg index cc6472de..e053ce2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,15 +40,15 @@ per-file-ignores = mpt_api_client/resources/catalog/*.py: WPS110 WPS214 WPS215 mpt_api_client/resources/catalog/products.py: WPS204 WPS214 WPS215 mpt_api_client/rql/query_builder.py: WPS110 WPS115 WPS210 WPS214 - tests/http/test_async_service.py: WPS204 WPS202 - tests/http/test_service.py: WPS204 WPS202 - tests/http/test_mixins.py: WPS204 WPS202 - tests/resources/catalog/test_products.py: WPS202 WPS210 - tests/resources/*/test_mixins.py: WPS118 WPS202 WPS204 WPS235 - tests/resources/accounts/test_users.py: WPS204 WPS202 WPS210 - tests/test_mpt_client.py: WPS235 - - tests/*: + tests/unit/http/test_async_service.py: WPS204 WPS202 + tests/unit/http/test_service.py: WPS204 WPS202 + tests/unit/http/test_mixins.py: WPS204 WPS202 + tests/unit/resources/catalog/test_products.py: WPS202 WPS210 + tests/unit/resources/*/test_mixins.py: WPS118 WPS202 WPS204 WPS235 + tests/unit/resources/accounts/test_users.py: WPS204 WPS202 WPS210 + tests/unit/test_mpt_client.py: WPS235 + + tests/unit/*: # Allow magic strings. WPS432 # Found too many modules members. diff --git a/sonar-project.properties b/sonar-project.properties index 8b00e074..dbd75587 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,5 +9,5 @@ sonar.inclusions=mpt_api_client/** sonar.exclusions=tests/**,**/__init__.py sonar.python.coverage.reportPaths=coverage.xml -sonar.python.xunit.reportPath=coverage.xml +sonar.python.xunit.reportPath=coverage.xml,e2e-report.xml sonar.python.version=3 diff --git a/tests/resources/billing/__init__.py b/tests/__init__.py similarity index 100% rename from tests/resources/billing/__init__.py rename to tests/__init__.py diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py new file mode 100644 index 00000000..e79230e0 --- /dev/null +++ b/tests/e2e/conftest.py @@ -0,0 +1,20 @@ +import os + +import pytest + +from mpt_api_client import MPTClient + + +@pytest.fixture +def api_token(): + return os.getenv("MPT_API_TOKEN") + + +@pytest.fixture +def base_url(): + return os.getenv("MPT_API_BASE_URL") + + +@pytest.fixture +def mpt_client(api_token, base_url): + return MPTClient.from_config(api_token=api_token, base_url=base_url) diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py new file mode 100644 index 00000000..ee5e65a4 --- /dev/null +++ b/tests/e2e/test_e2e.py @@ -0,0 +1,29 @@ +import random + +import pytest + +from mpt_api_client import MPTClient +from mpt_api_client.exceptions import MPTAPIError + + +@pytest.mark.flaky(reruns=5, reruns_delay=0.01) # noqa: WPS432 +@pytest.mark.e2e +def test_example(): + assert random.choice([True, False]) # noqa: S311 + + +@pytest.mark.flaky +@pytest.mark.e2e +def test_unauthorised(base_url): + client = MPTClient.from_config(api_token="TKN-invalid", base_url=base_url) # noqa: S106 + + with pytest.raises(MPTAPIError, match=r"401 Unauthorized"): + client.catalog.products.fetch_page() + + +@pytest.mark.flaky +@pytest.mark.e2e +def test_access(mpt_client): + product = mpt_client.catalog.products.get("PRD-1975-5250") + assert product.id == "PRD-1975-5250" + assert product.name == "Amazon Web Services" diff --git a/tests/resources/catalog/__init__.py b/tests/unit/__init__.py similarity index 100% rename from tests/resources/catalog/__init__.py rename to tests/unit/__init__.py diff --git a/tests/conftest.py b/tests/unit/conftest.py similarity index 100% rename from tests/conftest.py rename to tests/unit/conftest.py diff --git a/tests/http/conftest.py b/tests/unit/http/conftest.py similarity index 98% rename from tests/http/conftest.py rename to tests/unit/http/conftest.py index 892ffce8..821eadce 100644 --- a/tests/http/conftest.py +++ b/tests/unit/http/conftest.py @@ -12,7 +12,7 @@ CollectionMixin, ManagedResourceMixin, ) -from tests.conftest import DummyModel +from tests.unit.conftest import DummyModel class DummyService( # noqa: WPS215 diff --git a/tests/http/test_async_client.py b/tests/unit/http/test_async_client.py similarity index 97% rename from tests/http/test_async_client.py rename to tests/unit/http/test_async_client.py index 85452d90..c19888b5 100644 --- a/tests/http/test_async_client.py +++ b/tests/unit/http/test_async_client.py @@ -6,7 +6,7 @@ from mpt_api_client.exceptions import MPTError from mpt_api_client.http.async_client import AsyncHTTPClient -from tests.conftest import API_TOKEN, API_URL +from tests.unit.conftest import API_TOKEN, API_URL @pytest.fixture diff --git a/tests/http/test_base_service.py b/tests/unit/http/test_base_service.py similarity index 96% rename from tests/http/test_base_service.py rename to tests/unit/http/test_base_service.py index 6189b674..a4b1afec 100644 --- a/tests/http/test_base_service.py +++ b/tests/unit/http/test_base_service.py @@ -1,7 +1,7 @@ from mpt_api_client.http import Service from mpt_api_client.http.query_state import QueryState -from tests.conftest import DummyModel -from tests.http.conftest import DummyService +from tests.unit.conftest import DummyModel +from tests.unit.http.conftest import DummyService class ParametrisedDummyService( # noqa: WPS215 diff --git a/tests/http/test_client.py b/tests/unit/http/test_client.py similarity index 97% rename from tests/http/test_client.py rename to tests/unit/http/test_client.py index 313c45e1..3970ea93 100644 --- a/tests/http/test_client.py +++ b/tests/unit/http/test_client.py @@ -6,7 +6,7 @@ from mpt_api_client.exceptions import MPTError from mpt_api_client.http.client import HTTPClient -from tests.conftest import API_TOKEN, API_URL +from tests.unit.conftest import API_TOKEN, API_URL def test_http_initialization(mocker): diff --git a/tests/http/test_mixins.py b/tests/unit/http/test_mixins.py similarity index 99% rename from tests/http/test_mixins.py rename to tests/unit/http/test_mixins.py index 7dcce28e..f401d8c8 100644 --- a/tests/http/test_mixins.py +++ b/tests/unit/http/test_mixins.py @@ -17,7 +17,7 @@ AsyncMediaService, MediaService, ) -from tests.conftest import DummyModel +from tests.unit.conftest import DummyModel @pytest.fixture diff --git a/tests/http/test_query_state.py b/tests/unit/http/test_query_state.py similarity index 100% rename from tests/http/test_query_state.py rename to tests/unit/http/test_query_state.py diff --git a/tests/models/collection/conftest.py b/tests/unit/models/collection/conftest.py similarity index 94% rename from tests/models/collection/conftest.py rename to tests/unit/models/collection/conftest.py index 2f17e812..e8cc10ae 100644 --- a/tests/models/collection/conftest.py +++ b/tests/unit/models/collection/conftest.py @@ -1,7 +1,7 @@ import pytest from mpt_api_client.models import Collection -from tests.conftest import DummyModel +from tests.unit.conftest import DummyModel @pytest.fixture diff --git a/tests/models/collection/test_collection_init.py b/tests/unit/models/collection/test_collection_init.py similarity index 100% rename from tests/models/collection/test_collection_init.py rename to tests/unit/models/collection/test_collection_init.py diff --git a/tests/models/collection/test_collection_iteration.py b/tests/unit/models/collection/test_collection_iteration.py similarity index 100% rename from tests/models/collection/test_collection_iteration.py rename to tests/unit/models/collection/test_collection_iteration.py diff --git a/tests/models/collection/test_collection_list.py b/tests/unit/models/collection/test_collection_list.py similarity index 100% rename from tests/models/collection/test_collection_list.py rename to tests/unit/models/collection/test_collection_list.py diff --git a/tests/models/meta/test_meta.py b/tests/unit/models/meta/test_meta.py similarity index 100% rename from tests/models/meta/test_meta.py rename to tests/unit/models/meta/test_meta.py diff --git a/tests/models/meta/test_pagination.py b/tests/unit/models/meta/test_pagination.py similarity index 100% rename from tests/models/meta/test_pagination.py rename to tests/unit/models/meta/test_pagination.py diff --git a/tests/models/resource/test_resource.py b/tests/unit/models/resource/test_resource.py similarity index 100% rename from tests/models/resource/test_resource.py rename to tests/unit/models/resource/test_resource.py diff --git a/tests/models/resource/test_resource_custom_key.py b/tests/unit/models/resource/test_resource_custom_key.py similarity index 100% rename from tests/models/resource/test_resource_custom_key.py rename to tests/unit/models/resource/test_resource_custom_key.py diff --git a/tests/models/test_file_model.py b/tests/unit/models/test_file_model.py similarity index 100% rename from tests/models/test_file_model.py rename to tests/unit/models/test_file_model.py diff --git a/tests/unit/resources/__init__.py b/tests/unit/resources/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/resources/accounts/__init__.py b/tests/unit/resources/accounts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/accounts/test_account.py b/tests/unit/resources/accounts/test_account.py similarity index 100% rename from tests/resources/accounts/test_account.py rename to tests/unit/resources/accounts/test_account.py diff --git a/tests/resources/accounts/test_account_user_groups.py b/tests/unit/resources/accounts/test_account_user_groups.py similarity index 100% rename from tests/resources/accounts/test_account_user_groups.py rename to tests/unit/resources/accounts/test_account_user_groups.py diff --git a/tests/resources/accounts/test_account_users.py b/tests/unit/resources/accounts/test_account_users.py similarity index 100% rename from tests/resources/accounts/test_account_users.py rename to tests/unit/resources/accounts/test_account_users.py diff --git a/tests/resources/accounts/test_accounts.py b/tests/unit/resources/accounts/test_accounts.py similarity index 100% rename from tests/resources/accounts/test_accounts.py rename to tests/unit/resources/accounts/test_accounts.py diff --git a/tests/resources/accounts/test_accounts_user_groups.py b/tests/unit/resources/accounts/test_accounts_user_groups.py similarity index 100% rename from tests/resources/accounts/test_accounts_user_groups.py rename to tests/unit/resources/accounts/test_accounts_user_groups.py diff --git a/tests/resources/accounts/test_accounts_users.py b/tests/unit/resources/accounts/test_accounts_users.py similarity index 100% rename from tests/resources/accounts/test_accounts_users.py rename to tests/unit/resources/accounts/test_accounts_users.py diff --git a/tests/resources/accounts/test_api_tokens.py b/tests/unit/resources/accounts/test_api_tokens.py similarity index 100% rename from tests/resources/accounts/test_api_tokens.py rename to tests/unit/resources/accounts/test_api_tokens.py diff --git a/tests/resources/accounts/test_buyers.py b/tests/unit/resources/accounts/test_buyers.py similarity index 100% rename from tests/resources/accounts/test_buyers.py rename to tests/unit/resources/accounts/test_buyers.py diff --git a/tests/resources/accounts/test_cloud_tenants.py b/tests/unit/resources/accounts/test_cloud_tenants.py similarity index 100% rename from tests/resources/accounts/test_cloud_tenants.py rename to tests/unit/resources/accounts/test_cloud_tenants.py diff --git a/tests/resources/accounts/test_erp_links.py b/tests/unit/resources/accounts/test_erp_links.py similarity index 100% rename from tests/resources/accounts/test_erp_links.py rename to tests/unit/resources/accounts/test_erp_links.py diff --git a/tests/resources/accounts/test_licensees.py b/tests/unit/resources/accounts/test_licensees.py similarity index 100% rename from tests/resources/accounts/test_licensees.py rename to tests/unit/resources/accounts/test_licensees.py diff --git a/tests/resources/accounts/test_mixins.py b/tests/unit/resources/accounts/test_mixins.py similarity index 99% rename from tests/resources/accounts/test_mixins.py rename to tests/unit/resources/accounts/test_mixins.py index f9b0954b..d3c07946 100644 --- a/tests/resources/accounts/test_mixins.py +++ b/tests/unit/resources/accounts/test_mixins.py @@ -15,7 +15,7 @@ InvitableMixin, ValidateMixin, ) -from tests.conftest import DummyModel +from tests.unit.conftest import DummyModel class DummyActivatableService( diff --git a/tests/resources/accounts/test_modules.py b/tests/unit/resources/accounts/test_modules.py similarity index 100% rename from tests/resources/accounts/test_modules.py rename to tests/unit/resources/accounts/test_modules.py diff --git a/tests/resources/accounts/test_sellers.py b/tests/unit/resources/accounts/test_sellers.py similarity index 100% rename from tests/resources/accounts/test_sellers.py rename to tests/unit/resources/accounts/test_sellers.py diff --git a/tests/resources/accounts/test_user_groups.py b/tests/unit/resources/accounts/test_user_groups.py similarity index 100% rename from tests/resources/accounts/test_user_groups.py rename to tests/unit/resources/accounts/test_user_groups.py diff --git a/tests/resources/accounts/test_users.py b/tests/unit/resources/accounts/test_users.py similarity index 100% rename from tests/resources/accounts/test_users.py rename to tests/unit/resources/accounts/test_users.py diff --git a/tests/resources/audit/test_audit.py b/tests/unit/resources/audit/test_audit.py similarity index 100% rename from tests/resources/audit/test_audit.py rename to tests/unit/resources/audit/test_audit.py diff --git a/tests/resources/audit/test_event_types.py b/tests/unit/resources/audit/test_event_types.py similarity index 100% rename from tests/resources/audit/test_event_types.py rename to tests/unit/resources/audit/test_event_types.py diff --git a/tests/resources/audit/test_records.py b/tests/unit/resources/audit/test_records.py similarity index 100% rename from tests/resources/audit/test_records.py rename to tests/unit/resources/audit/test_records.py diff --git a/tests/unit/resources/billing/__init__.py b/tests/unit/resources/billing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/billing/test_billing.py b/tests/unit/resources/billing/test_billing.py similarity index 100% rename from tests/resources/billing/test_billing.py rename to tests/unit/resources/billing/test_billing.py diff --git a/tests/resources/billing/test_credit_memo_attachments.py b/tests/unit/resources/billing/test_credit_memo_attachments.py similarity index 100% rename from tests/resources/billing/test_credit_memo_attachments.py rename to tests/unit/resources/billing/test_credit_memo_attachments.py diff --git a/tests/resources/billing/test_credit_memos.py b/tests/unit/resources/billing/test_credit_memos.py similarity index 100% rename from tests/resources/billing/test_credit_memos.py rename to tests/unit/resources/billing/test_credit_memos.py diff --git a/tests/resources/billing/test_custom_ledger_attachments.py b/tests/unit/resources/billing/test_custom_ledger_attachments.py similarity index 100% rename from tests/resources/billing/test_custom_ledger_attachments.py rename to tests/unit/resources/billing/test_custom_ledger_attachments.py diff --git a/tests/resources/billing/test_custom_ledger_charges.py b/tests/unit/resources/billing/test_custom_ledger_charges.py similarity index 100% rename from tests/resources/billing/test_custom_ledger_charges.py rename to tests/unit/resources/billing/test_custom_ledger_charges.py diff --git a/tests/resources/billing/test_custom_ledger_upload.py b/tests/unit/resources/billing/test_custom_ledger_upload.py similarity index 100% rename from tests/resources/billing/test_custom_ledger_upload.py rename to tests/unit/resources/billing/test_custom_ledger_upload.py diff --git a/tests/resources/billing/test_custom_ledgers.py b/tests/unit/resources/billing/test_custom_ledgers.py similarity index 100% rename from tests/resources/billing/test_custom_ledgers.py rename to tests/unit/resources/billing/test_custom_ledgers.py diff --git a/tests/resources/billing/test_invoice_attachments.py b/tests/unit/resources/billing/test_invoice_attachments.py similarity index 100% rename from tests/resources/billing/test_invoice_attachments.py rename to tests/unit/resources/billing/test_invoice_attachments.py diff --git a/tests/resources/billing/test_invoices.py b/tests/unit/resources/billing/test_invoices.py similarity index 100% rename from tests/resources/billing/test_invoices.py rename to tests/unit/resources/billing/test_invoices.py diff --git a/tests/resources/billing/test_journal_attachments.py b/tests/unit/resources/billing/test_journal_attachments.py similarity index 100% rename from tests/resources/billing/test_journal_attachments.py rename to tests/unit/resources/billing/test_journal_attachments.py diff --git a/tests/resources/billing/test_journal_charges.py b/tests/unit/resources/billing/test_journal_charges.py similarity index 100% rename from tests/resources/billing/test_journal_charges.py rename to tests/unit/resources/billing/test_journal_charges.py diff --git a/tests/resources/billing/test_journal_sellers.py b/tests/unit/resources/billing/test_journal_sellers.py similarity index 100% rename from tests/resources/billing/test_journal_sellers.py rename to tests/unit/resources/billing/test_journal_sellers.py diff --git a/tests/resources/billing/test_journal_upload.py b/tests/unit/resources/billing/test_journal_upload.py similarity index 100% rename from tests/resources/billing/test_journal_upload.py rename to tests/unit/resources/billing/test_journal_upload.py diff --git a/tests/resources/billing/test_journals.py b/tests/unit/resources/billing/test_journals.py similarity index 100% rename from tests/resources/billing/test_journals.py rename to tests/unit/resources/billing/test_journals.py diff --git a/tests/resources/billing/test_ledger_attachments.py b/tests/unit/resources/billing/test_ledger_attachments.py similarity index 100% rename from tests/resources/billing/test_ledger_attachments.py rename to tests/unit/resources/billing/test_ledger_attachments.py diff --git a/tests/resources/billing/test_ledger_charges.py b/tests/unit/resources/billing/test_ledger_charges.py similarity index 100% rename from tests/resources/billing/test_ledger_charges.py rename to tests/unit/resources/billing/test_ledger_charges.py diff --git a/tests/resources/billing/test_ledgers.py b/tests/unit/resources/billing/test_ledgers.py similarity index 100% rename from tests/resources/billing/test_ledgers.py rename to tests/unit/resources/billing/test_ledgers.py diff --git a/tests/resources/billing/test_manual_overrides.py b/tests/unit/resources/billing/test_manual_overrides.py similarity index 100% rename from tests/resources/billing/test_manual_overrides.py rename to tests/unit/resources/billing/test_manual_overrides.py diff --git a/tests/resources/billing/test_mixins.py b/tests/unit/resources/billing/test_mixins.py similarity index 99% rename from tests/resources/billing/test_mixins.py rename to tests/unit/resources/billing/test_mixins.py index a72c0530..c53abb8a 100644 --- a/tests/resources/billing/test_mixins.py +++ b/tests/unit/resources/billing/test_mixins.py @@ -14,7 +14,7 @@ RecalculatableMixin, RegeneratableMixin, ) -from tests.conftest import DummyModel +from tests.unit.conftest import DummyModel class DummyRegeneratableService( diff --git a/tests/resources/billing/test_statement_charges.py b/tests/unit/resources/billing/test_statement_charges.py similarity index 100% rename from tests/resources/billing/test_statement_charges.py rename to tests/unit/resources/billing/test_statement_charges.py diff --git a/tests/resources/billing/test_statements.py b/tests/unit/resources/billing/test_statements.py similarity index 100% rename from tests/resources/billing/test_statements.py rename to tests/unit/resources/billing/test_statements.py diff --git a/tests/unit/resources/catalog/__init__.py b/tests/unit/resources/catalog/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/catalog/test_authorizations.py b/tests/unit/resources/catalog/test_authorizations.py similarity index 100% rename from tests/resources/catalog/test_authorizations.py rename to tests/unit/resources/catalog/test_authorizations.py diff --git a/tests/resources/catalog/test_catalog.py b/tests/unit/resources/catalog/test_catalog.py similarity index 100% rename from tests/resources/catalog/test_catalog.py rename to tests/unit/resources/catalog/test_catalog.py diff --git a/tests/resources/catalog/test_items.py b/tests/unit/resources/catalog/test_items.py similarity index 100% rename from tests/resources/catalog/test_items.py rename to tests/unit/resources/catalog/test_items.py diff --git a/tests/resources/catalog/test_listings.py b/tests/unit/resources/catalog/test_listings.py similarity index 100% rename from tests/resources/catalog/test_listings.py rename to tests/unit/resources/catalog/test_listings.py diff --git a/tests/resources/catalog/test_mixins.py b/tests/unit/resources/catalog/test_mixins.py similarity index 99% rename from tests/resources/catalog/test_mixins.py rename to tests/unit/resources/catalog/test_mixins.py index da38bc1a..1310e3f0 100644 --- a/tests/resources/catalog/test_mixins.py +++ b/tests/unit/resources/catalog/test_mixins.py @@ -10,7 +10,7 @@ AsyncPublishableMixin, PublishableMixin, ) -from tests.conftest import DummyModel +from tests.unit.conftest import DummyModel class DummyPublishableService( # noqa: WPS215 diff --git a/tests/resources/catalog/test_price_list_items.py b/tests/unit/resources/catalog/test_price_list_items.py similarity index 100% rename from tests/resources/catalog/test_price_list_items.py rename to tests/unit/resources/catalog/test_price_list_items.py diff --git a/tests/resources/catalog/test_price_lists.py b/tests/unit/resources/catalog/test_price_lists.py similarity index 100% rename from tests/resources/catalog/test_price_lists.py rename to tests/unit/resources/catalog/test_price_lists.py diff --git a/tests/resources/catalog/test_pricing_policies.py b/tests/unit/resources/catalog/test_pricing_policies.py similarity index 100% rename from tests/resources/catalog/test_pricing_policies.py rename to tests/unit/resources/catalog/test_pricing_policies.py diff --git a/tests/resources/catalog/test_pricing_policy_attachments.py b/tests/unit/resources/catalog/test_pricing_policy_attachments.py similarity index 100% rename from tests/resources/catalog/test_pricing_policy_attachments.py rename to tests/unit/resources/catalog/test_pricing_policy_attachments.py diff --git a/tests/resources/catalog/test_product_term_variants.py b/tests/unit/resources/catalog/test_product_term_variants.py similarity index 100% rename from tests/resources/catalog/test_product_term_variants.py rename to tests/unit/resources/catalog/test_product_term_variants.py diff --git a/tests/resources/catalog/test_product_terms.py b/tests/unit/resources/catalog/test_product_terms.py similarity index 100% rename from tests/resources/catalog/test_product_terms.py rename to tests/unit/resources/catalog/test_product_terms.py diff --git a/tests/resources/catalog/test_products.py b/tests/unit/resources/catalog/test_products.py similarity index 100% rename from tests/resources/catalog/test_products.py rename to tests/unit/resources/catalog/test_products.py diff --git a/tests/resources/catalog/test_products_documents.py b/tests/unit/resources/catalog/test_products_documents.py similarity index 100% rename from tests/resources/catalog/test_products_documents.py rename to tests/unit/resources/catalog/test_products_documents.py diff --git a/tests/resources/catalog/test_products_item_groups.py b/tests/unit/resources/catalog/test_products_item_groups.py similarity index 100% rename from tests/resources/catalog/test_products_item_groups.py rename to tests/unit/resources/catalog/test_products_item_groups.py diff --git a/tests/resources/catalog/test_products_media.py b/tests/unit/resources/catalog/test_products_media.py similarity index 100% rename from tests/resources/catalog/test_products_media.py rename to tests/unit/resources/catalog/test_products_media.py diff --git a/tests/resources/catalog/test_products_parameter_groups.py b/tests/unit/resources/catalog/test_products_parameter_groups.py similarity index 100% rename from tests/resources/catalog/test_products_parameter_groups.py rename to tests/unit/resources/catalog/test_products_parameter_groups.py diff --git a/tests/resources/catalog/test_products_parameters.py b/tests/unit/resources/catalog/test_products_parameters.py similarity index 100% rename from tests/resources/catalog/test_products_parameters.py rename to tests/unit/resources/catalog/test_products_parameters.py diff --git a/tests/resources/catalog/test_products_templates.py b/tests/unit/resources/catalog/test_products_templates.py similarity index 100% rename from tests/resources/catalog/test_products_templates.py rename to tests/unit/resources/catalog/test_products_templates.py diff --git a/tests/resources/catalog/test_units_of_measure.py b/tests/unit/resources/catalog/test_units_of_measure.py similarity index 100% rename from tests/resources/catalog/test_units_of_measure.py rename to tests/unit/resources/catalog/test_units_of_measure.py diff --git a/tests/unit/resources/commerce/__init__.py b/tests/unit/resources/commerce/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/commerce/test_agreements.py b/tests/unit/resources/commerce/test_agreements.py similarity index 100% rename from tests/resources/commerce/test_agreements.py rename to tests/unit/resources/commerce/test_agreements.py diff --git a/tests/resources/commerce/test_agreements_attachments.py b/tests/unit/resources/commerce/test_agreements_attachments.py similarity index 100% rename from tests/resources/commerce/test_agreements_attachments.py rename to tests/unit/resources/commerce/test_agreements_attachments.py diff --git a/tests/resources/commerce/test_commerce.py b/tests/unit/resources/commerce/test_commerce.py similarity index 100% rename from tests/resources/commerce/test_commerce.py rename to tests/unit/resources/commerce/test_commerce.py diff --git a/tests/resources/commerce/test_order_subcription.py b/tests/unit/resources/commerce/test_order_subcription.py similarity index 100% rename from tests/resources/commerce/test_order_subcription.py rename to tests/unit/resources/commerce/test_order_subcription.py diff --git a/tests/resources/commerce/test_orders.py b/tests/unit/resources/commerce/test_orders.py similarity index 100% rename from tests/resources/commerce/test_orders.py rename to tests/unit/resources/commerce/test_orders.py diff --git a/tests/resources/commerce/test_subscriptions.py b/tests/unit/resources/commerce/test_subscriptions.py similarity index 100% rename from tests/resources/commerce/test_subscriptions.py rename to tests/unit/resources/commerce/test_subscriptions.py diff --git a/tests/unit/resources/notifications/__init__.py b/tests/unit/resources/notifications/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/resources/notifications/test_accounts.py b/tests/unit/resources/notifications/test_accounts.py similarity index 100% rename from tests/resources/notifications/test_accounts.py rename to tests/unit/resources/notifications/test_accounts.py diff --git a/tests/resources/notifications/test_batches.py b/tests/unit/resources/notifications/test_batches.py similarity index 100% rename from tests/resources/notifications/test_batches.py rename to tests/unit/resources/notifications/test_batches.py diff --git a/tests/resources/notifications/test_categories.py b/tests/unit/resources/notifications/test_categories.py similarity index 100% rename from tests/resources/notifications/test_categories.py rename to tests/unit/resources/notifications/test_categories.py diff --git a/tests/resources/notifications/test_contacts.py b/tests/unit/resources/notifications/test_contacts.py similarity index 100% rename from tests/resources/notifications/test_contacts.py rename to tests/unit/resources/notifications/test_contacts.py diff --git a/tests/resources/notifications/test_messages.py b/tests/unit/resources/notifications/test_messages.py similarity index 100% rename from tests/resources/notifications/test_messages.py rename to tests/unit/resources/notifications/test_messages.py diff --git a/tests/resources/notifications/test_notifications.py b/tests/unit/resources/notifications/test_notifications.py similarity index 100% rename from tests/resources/notifications/test_notifications.py rename to tests/unit/resources/notifications/test_notifications.py diff --git a/tests/resources/notifications/test_subscribers.py b/tests/unit/resources/notifications/test_subscribers.py similarity index 100% rename from tests/resources/notifications/test_subscribers.py rename to tests/unit/resources/notifications/test_subscribers.py diff --git a/tests/unit/rql/__init__.py b/tests/unit/rql/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/rql/query_builder/__init__.py b/tests/unit/rql/query_builder/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/rql/query_builder/test_create_rql.py b/tests/unit/rql/query_builder/test_create_rql.py similarity index 100% rename from tests/rql/query_builder/test_create_rql.py rename to tests/unit/rql/query_builder/test_create_rql.py diff --git a/tests/rql/query_builder/test_multiple_ops.py b/tests/unit/rql/query_builder/test_multiple_ops.py similarity index 100% rename from tests/rql/query_builder/test_multiple_ops.py rename to tests/unit/rql/query_builder/test_multiple_ops.py diff --git a/tests/rql/query_builder/test_rql.py b/tests/unit/rql/query_builder/test_rql.py similarity index 100% rename from tests/rql/query_builder/test_rql.py rename to tests/unit/rql/query_builder/test_rql.py diff --git a/tests/rql/query_builder/test_rql_all_any.py b/tests/unit/rql/query_builder/test_rql_all_any.py similarity index 100% rename from tests/rql/query_builder/test_rql_all_any.py rename to tests/unit/rql/query_builder/test_rql_all_any.py diff --git a/tests/rql/query_builder/test_rql_and.py b/tests/unit/rql/query_builder/test_rql_and.py similarity index 100% rename from tests/rql/query_builder/test_rql_and.py rename to tests/unit/rql/query_builder/test_rql_and.py diff --git a/tests/rql/query_builder/test_rql_dot_path.py b/tests/unit/rql/query_builder/test_rql_dot_path.py similarity index 100% rename from tests/rql/query_builder/test_rql_dot_path.py rename to tests/unit/rql/query_builder/test_rql_dot_path.py diff --git a/tests/rql/query_builder/test_rql_eq.py b/tests/unit/rql/query_builder/test_rql_eq.py similarity index 100% rename from tests/rql/query_builder/test_rql_eq.py rename to tests/unit/rql/query_builder/test_rql_eq.py diff --git a/tests/rql/query_builder/test_rql_in.py b/tests/unit/rql/query_builder/test_rql_in.py similarity index 100% rename from tests/rql/query_builder/test_rql_in.py rename to tests/unit/rql/query_builder/test_rql_in.py diff --git a/tests/rql/query_builder/test_rql_or.py b/tests/unit/rql/query_builder/test_rql_or.py similarity index 100% rename from tests/rql/query_builder/test_rql_or.py rename to tests/unit/rql/query_builder/test_rql_or.py diff --git a/tests/rql/query_builder/test_rql_parse_kwargs.py b/tests/unit/rql/query_builder/test_rql_parse_kwargs.py similarity index 100% rename from tests/rql/query_builder/test_rql_parse_kwargs.py rename to tests/unit/rql/query_builder/test_rql_parse_kwargs.py diff --git a/tests/test_exceptions.py b/tests/unit/test_exceptions.py similarity index 60% rename from tests/test_exceptions.py rename to tests/unit/test_exceptions.py index 10ee6c5f..235728e9 100644 --- a/tests/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -10,10 +10,34 @@ def test_http_error(): - exception = MPTHttpError(status_code=400, text="Content") + exception = MPTHttpError(status_code=400, message="Bad request", body="Content") assert exception.status_code == 400 - assert exception.text == "Content" + assert exception.body == "Content" + assert str(exception) == "HTTP 400: Bad request" + + +def test_http_error_not_found_from_mpt(): # noqa: WPS218 + status_code = 400 # changed from 404 for testing purposes + api_status_code = 404 + payload = {"message": "Resource not found", "statusCode": api_status_code} + message = ( + "Client error '404 Resource Not Found' for url " + "'https://api.s1.show/public/public/v1/catalog/products?limit=100&offset=0'\n" + "For more information check: " + "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404" + ) + + exception = MPTAPIError(status_code=status_code, message=message, payload=payload) + + assert exception.status_code == status_code + assert exception.payload == payload + assert exception.status == api_status_code + assert exception.title == "Resource not found" + assert exception.detail == message + assert exception.trace_id is None + assert exception.errors is None + assert str(exception) == f"404 Resource not found - {message} (no-trace-id)" def test_api_error(): # noqa: WPS218 @@ -24,7 +48,7 @@ def test_api_error(): # noqa: WPS218 "traceId": "abc123", "errors": "Some error details", } - exception = MPTAPIError(status_code=400, payload=payload) + exception = MPTAPIError(status_code=400, message="Bad Request", payload=payload) assert exception.status_code == 400 assert exception.payload == payload @@ -43,7 +67,7 @@ def test_api_error_str_and_repr(): "traceId": "abc123", "errors": "Some error details", } - exception = MPTAPIError(status_code=400, payload=payload) + exception = MPTAPIError(status_code=400, message="Bad request", payload=payload) assert str(exception) == '400 Bad Request - Invalid input (abc123)\n"Some error details"' assert repr(exception) == ( @@ -60,12 +84,12 @@ def test_api_error_str_no_errors(): "traceId": "abc123", } - exception = MPTAPIError(status_code=400, payload=payload) + exception = MPTAPIError(status_code=400, message="Bad request", payload=payload) assert str(exception) == "400 Bad Request - Invalid input (abc123)" -def test_transform_http_status_exception(): +def test_transform_http_status_exception_api(): payload = { "status": "400", "title": "Bad Request", @@ -88,17 +112,18 @@ def test_transform_http_status_exception(): assert err.payload == payload -def test_transform_http_status_exception_json(): +def test_transform_http_status_exception(): response = Response( status_code=500, request=Request("GET", "http://test"), content=b"Internal Server Error", headers={"content-type": "text/plain"}, ) - exc = HTTPStatusError("error", request=response.request, response=response) + exc = HTTPStatusError("Error message", request=response.request, response=response) err = transform_http_status_exception(exc) assert isinstance(err, MPTHttpError) assert err.status_code == 500 - assert err.text == "Internal Server Error" + assert err.body == "Internal Server Error" + assert str(err) == "HTTP 500: Error message" diff --git a/tests/test_mpt_client.py b/tests/unit/test_mpt_client.py similarity index 97% rename from tests/test_mpt_client.py rename to tests/unit/test_mpt_client.py index 24f53fe3..03dabee2 100644 --- a/tests/test_mpt_client.py +++ b/tests/unit/test_mpt_client.py @@ -16,7 +16,7 @@ Commerce, Notifications, ) -from tests.conftest import API_TOKEN, API_URL +from tests.unit.conftest import API_TOKEN, API_URL def get_mpt_client(): diff --git a/uv.lock b/uv.lock index 3705493f..ad415151 100644 --- a/uv.lock +++ b/uv.lock @@ -396,6 +396,7 @@ dev = [ { name = "pytest-deadfixtures" }, { name = "pytest-mock" }, { name = "pytest-randomly" }, + { name = "pytest-rerunfailures" }, { name = "pytest-xdist" }, { name = "responses" }, { name = "respx" }, @@ -423,6 +424,7 @@ dev = [ { name = "pytest-deadfixtures", specifier = "==2.2.*" }, { name = "pytest-mock", specifier = "==3.14.*" }, { name = "pytest-randomly", specifier = "==3.16.*" }, + { name = "pytest-rerunfailures", specifier = ">=16.1" }, { name = "pytest-xdist", specifier = "==3.6.*" }, { name = "responses", specifier = "==0.25.*" }, { name = "respx", specifier = "==0.22.*" }, @@ -671,6 +673,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/70/b31577d7c46d8e2f9baccfed5067dd8475262a2331ffb0bfdf19361c9bde/pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6", size = 8396, upload-time = "2024-10-25T15:45:32.78Z" }, ] +[[package]] +name = "pytest-rerunfailures" +version = "16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/04/71e9520551fc8fe2cf5c1a1842e4e600265b0815f2016b7c27ec85688682/pytest_rerunfailures-16.1.tar.gz", hash = "sha256:c38b266db8a808953ebd71ac25c381cb1981a78ff9340a14bcb9f1b9bff1899e", size = 30889, upload-time = "2025-10-10T07:06:01.238Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/54/60eabb34445e3db3d3d874dc1dfa72751bfec3265bd611cb13c8b290adea/pytest_rerunfailures-16.1-py3-none-any.whl", hash = "sha256:5d11b12c0ca9a1665b5054052fcc1084f8deadd9328962745ef6b04e26382e86", size = 14093, upload-time = "2025-10-10T07:06:00.019Z" }, +] + [[package]] name = "pytest-xdist" version = "3.6.1"