From c83d29430601243cd79bbf84182071da538d26e3 Mon Sep 17 00:00:00 2001 From: Lukasz Lancucki Date: Tue, 23 Jun 2026 11:30:26 +0100 Subject: [PATCH] test(e2e): generate unique currency code avoiding real ISO codes [MPT-21554] The created_currency fixture derived the 3-letter currency code from 3 hex digits mapped onto a 16-letter alphabet (A-P), giving only ~4096 possible codes. Those collided with the real ISO 4217 codes and with leftover e2e currencies, so currencies_service.create intermittently failed with "400 Bad Request - Data is not unique". Generate the code from the full A-Z alphabet and regenerate whenever the candidate matches a real ISO 4217 code, sourced from the new iso4217 dev dependency. This removes the deterministic collision against real currencies and widens the random space, fixing the flaky setup. Log the generated code from the currency_data fixture at INFO so it is captured under "Captured log setup" and visible in failed-test output, making any future uniqueness failure traceable to the exact code used. The fixture prints are replaced with module-logger calls. Co-Authored-By: Claude Opus 4.8 --- pyproject.toml | 7 +++--- tests/e2e/exchange/currencies/conftest.py | 26 +++++++++++++++++++---- uv.lock | 11 ++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d6be511e..b4b201eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,9 @@ dev = [ "freezegun==1.5.*", "ipdb==0.13.*", "ipython==9.*", - "mypy==2.1.*", + "mypy==2.1.*", "pre-commit==4.6.*", - "pyfakefs==6.2.*", + "pyfakefs==6.2.*", "pytest==9.1.*", "pytest-asyncio==1.4.*", "pytest-cov==7.1.*", @@ -46,11 +46,12 @@ dev = [ "pytest-rerunfailures==16.3.*", "pytest-xdist==3.8.*", "responses==0.26.*", - "respx==0.23.*", + "respx==0.23.*", "ruff==0.15.*", # force ruff version to have same formatting everywhere "typing-extensions==4.15.*", "wemake-python-styleguide==1.6.*", "types-python-dateutil==2.9.*", + "iso4217==1.16.*", ] [tool.hatch.build.targets.sdist] diff --git a/tests/e2e/exchange/currencies/conftest.py b/tests/e2e/exchange/currencies/conftest.py index adb87541..370ac4e0 100644 --- a/tests/e2e/exchange/currencies/conftest.py +++ b/tests/e2e/exchange/currencies/conftest.py @@ -1,9 +1,27 @@ +import logging +import secrets import string import pytest +from iso4217 import Currency from mpt_api_client.exceptions import MPTAPIError +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +CURRENCY_CODE_LENGTH = 3 +CURRENCY_CODE_ALPHABET = string.ascii_uppercase +ISO_CURRENCY_CODES = frozenset(currency.code for currency in Currency) + + +def _random_currency_code(): + while True: + letters = (secrets.choice(CURRENCY_CODE_ALPHABET) for _ in range(CURRENCY_CODE_LENGTH)) + code = "".join(letters) + if code not in ISO_CURRENCY_CODES: + return code + @pytest.fixture def currencies_service(mpt_ops): @@ -22,8 +40,8 @@ def currency_id(e2e_config): @pytest.fixture def currency_data(short_uuid): - digit_to_alpha = str.maketrans(string.digits, "GHIJKLMNOP") - code = short_uuid[:3].translate(digit_to_alpha).upper() + code = _random_currency_code() + logger.info("e2e generated currency code: %s", code) return { "name": f"e2e - please delete {short_uuid}", "code": code, @@ -40,7 +58,7 @@ def created_currency(currencies_service, currency_data, logo_fd): try: currencies_service.delete(currency.id) except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete currency {currency.id}: {error.title}") # noqa: WPS421 + logger.warning("TEARDOWN - Unable to delete currency %s: %s", currency.id, error.title) @pytest.fixture @@ -52,4 +70,4 @@ async def async_created_currency(async_currencies_service, currency_data, logo_f try: await async_currencies_service.delete(currency.id) except MPTAPIError as error: - print(f"TEARDOWN - Unable to delete currency {currency.id}: {error.title}") # noqa: WPS421 + logger.warning("TEARDOWN - Unable to delete currency %s: %s", currency.id, error.title) diff --git a/uv.lock b/uv.lock index e8d4b67d..15c6e25b 100644 --- a/uv.lock +++ b/uv.lock @@ -691,6 +691,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, ] +[[package]] +name = "iso4217" +version = "1.16.20260101" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/98/a5f28006515c24de82c3d76f79e13dd3fbdf71431fdb158505ee45008908/iso4217-1.16.20260101.tar.gz", hash = "sha256:6a5afba6acb01cad8045f81e2b96f6adbea04f0c524ae21f317ac4ca11c3bdd6", size = 13695, upload-time = "2026-03-03T15:28:36.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/5b/725a6980401169ba9115f0b837d3af09134e43eaed9b2bebd8dbb50df733/iso4217-1.16.20260101-py2.py3-none-any.whl", hash = "sha256:69c591b93c7d783ec8c309ebee8df1aa28f8403d44bcce518ae8db050462084b", size = 11991, upload-time = "2026-03-03T15:28:34.523Z" }, +] + [[package]] name = "jedi" version = "0.19.2" @@ -802,6 +811,7 @@ dev = [ { name = "freezegun" }, { name = "ipdb" }, { name = "ipython" }, + { name = "iso4217" }, { name = "mypy" }, { name = "pre-commit" }, { name = "pyfakefs" }, @@ -837,6 +847,7 @@ dev = [ { name = "freezegun", specifier = "==1.5.*" }, { name = "ipdb", specifier = "==0.13.*" }, { name = "ipython", specifier = "==9.*" }, + { name = "iso4217", specifier = "==1.16.*" }, { name = "mypy", specifier = "==2.1.*" }, { name = "pre-commit", specifier = "==4.6.*" }, { name = "pyfakefs", specifier = "==6.2.*" },