From 5e24de9536efb37a29a99298266302acf96e38a8 Mon Sep 17 00:00:00 2001 From: Sosokker Date: Sun, 11 May 2025 17:04:15 +0700 Subject: [PATCH] add api_adapter test --- pyproject.toml | 2 + tests/__init__.py | 0 tests/test_api_adapter.py | 109 ++++++++++++++++++++++++++++++++++++++ uv.lock | 51 ++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/test_api_adapter.py diff --git a/pyproject.toml b/pyproject.toml index 57c76e8..7b3e243 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,8 @@ dependencies = [ "inquirer>=3.4.0", "loguru>=0.7.3", "pandas>=2.2.3", + "pytest>=8.3.5", "python-dotenv>=1.1.0", + "responses>=0.25.7", "rich>=14.0.0", ] diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_api_adapter.py b/tests/test_api_adapter.py new file mode 100644 index 0000000..a4fc65e --- /dev/null +++ b/tests/test_api_adapter.py @@ -0,0 +1,109 @@ +import responses +import pytest +from ingestion.adapters.api_adapter import ApiAdapter +import httpx + + +@pytest.fixture +def single_product(): + return "https://dummyjson.com/products/1" + +@pytest.fixture +def multiple_product(): + return "https://dummyjson.com/products" + +@pytest.fixture +def auth_endpoint(): + return "https://dummyjson.com/auth/login" + +@pytest.fixture +def auth_require_endpoint(): + return "https://dummyjson.com/auth/me" + + +def test_fetch_dict_response(single_product): + """Test fetching a single record from a JSON API endpoint.""" + response = httpx.get(single_product, timeout=10) + response.raise_for_status() + expected_data = response.json() + assert isinstance(expected_data, dict) + + adapter = ApiAdapter(url=single_product) + adapter_result = adapter.fetch() + + assert isinstance(adapter_result, list) + assert adapter_result[0] == expected_data + + +def test_fetch_list_response(multiple_product): + """Test fetching a list of records from a JSON API endpoint.""" + response = httpx.get(multiple_product, timeout=10) + response.raise_for_status() + expected_data = response.json() + + adapter = ApiAdapter(url=multiple_product) + adapter_result = adapter.fetch() + + assert adapter_result[0] == expected_data + + +@responses.activate +def test_fetch_http_error(single_product): + """Test handling HTTP errors and validate graceful failure.""" + for _ in range(4): + responses.add(responses.GET, single_product, status=500) + + adapter = ApiAdapter(url=single_product) + + with pytest.raises(RuntimeError) as exc_info: + adapter.fetch() + + assert "API request failed" in str(exc_info.value) + +@responses.activate +def test_fetch_json_decode_error(single_product): + """Test handling JSON decode errors.""" + responses.add(responses.GET, single_product, body="not-a-json", status=200) + + adapter = ApiAdapter(url=single_product) + + with pytest.raises(RuntimeError) as exc_info: + adapter.fetch() + + assert "Failed to parse JSON response" in str(exc_info.value) + + +def test_token_header_injection(auth_endpoint, auth_require_endpoint): + """Test that the token is injected into the Authorization header.""" + payload = { + "username": "emilys", + "password": "emilyspass", + "expiresInMins": 30 + } + + response = httpx.post( + auth_endpoint, + timeout=10, + headers={"Content-Type": "application/json"}, + json=payload + ) + + response.raise_for_status() + + assert response.status_code == 200 + + token = response.json().get("accessToken") + + adapter = ApiAdapter(url=auth_require_endpoint, token=token) + adapter_result = adapter.fetch() + + assert isinstance(adapter_result, list) + assert adapter_result[0].get("username") == "emilys" + + +def test_custom_headers_are_used(single_product): + """Test that custom headers are used.""" + headers = {"X-Custom-Header": "test-value"} + adapter = ApiAdapter(url=single_product, headers=headers) + + assert adapter.headers.get("X-Custom-Header") == "test-value" diff --git a/uv.lock b/uv.lock index 844a213..e765972 100644 --- a/uv.lock +++ b/uv.lock @@ -307,7 +307,9 @@ dependencies = [ { name = "inquirer" }, { name = "loguru" }, { name = "pandas" }, + { name = "pytest" }, { name = "python-dotenv" }, + { name = "responses" }, { name = "rich" }, ] @@ -318,7 +320,9 @@ requires-dist = [ { name = "inquirer", specifier = ">=3.4.0" }, { name = "loguru", specifier = ">=0.7.3" }, { name = "pandas", specifier = ">=2.2.3" }, + { name = "pytest", specifier = ">=8.3.5" }, { name = "python-dotenv", specifier = ">=1.1.0" }, + { name = "responses", specifier = ">=0.25.7" }, { name = "rich", specifier = ">=14.0.0" }, ] @@ -684,6 +688,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/9d/0fb148dc4d6fa4a7dd1d8378168d9b4cd8d4560a6fbf6f0121c5fc34eb68/importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e", size = 26971 }, ] +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + [[package]] name = "inquirer" version = "3.4.0" @@ -1152,6 +1165,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/0f/098488de02e3d52fc77e8d55c1467f6703701b6ea6788f40409bb8c00dd4/playwright-1.51.0-py3-none-win_amd64.whl", hash = "sha256:9ece9316c5d383aed1a207f079fc2d552fff92184f0ecf37cc596e912d00a8c3", size = 34862693 }, ] +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + [[package]] name = "propcache" version = "0.3.1" @@ -1330,6 +1352,21 @@ version = "1.9.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/23/2f0a3efc4d6a32f3b63cdff36cd398d9701d26cda58e3ab97ac79fb5e60d/pyperclip-1.9.0.tar.gz", hash = "sha256:b7de0142ddc81bfc5c7507eea19da920b92252b548b96186caf94a5e2527d310", size = 20961 } +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1483,6 +1520,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "responses" +version = "0.25.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/7e/2345ac3299bd62bd7163216702bbc88976c099cfceba5b889f2a457727a1/responses-0.25.7.tar.gz", hash = "sha256:8ebae11405d7a5df79ab6fd54277f6f2bc29b2d002d0dd2d5c632594d1ddcedb", size = 79203 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/fc/1d20b64fa90e81e4fa0a34c9b0240a6cfb1326b7e06d18a5432a9917c316/responses-0.25.7-py3-none-any.whl", hash = "sha256:92ca17416c90fe6b35921f52179bff29332076bb32694c0df02dcac2c6bc043c", size = 34732 }, +] + [[package]] name = "rich" version = "14.0.0"