From 44363ef60790b3e7ab5488fc7c334cf81219008d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:15:05 +0000 Subject: [PATCH 1/3] Add unit tests for the Blueprint API Client Added a comprehensive test suite for `enapter.http.api.blueprints.Client` covering get, upload (data/file/directory), download, and validation methods. Mocks `httpx.AsyncClient` responses to verify request structures and data parsing without making external network requests. Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- .../test_api/test_blueprints/__init__.py | 0 .../test_api/test_blueprints/test_client.py | 215 ++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 tests/unit/test_http/test_api/test_blueprints/__init__.py create mode 100644 tests/unit/test_http/test_api/test_blueprints/test_client.py diff --git a/tests/unit/test_http/test_api/test_blueprints/__init__.py b/tests/unit/test_http/test_api/test_blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/test_http/test_api/test_blueprints/test_client.py b/tests/unit/test_http/test_api/test_blueprints/test_client.py new file mode 100644 index 0000000..8651b55 --- /dev/null +++ b/tests/unit/test_http/test_api/test_blueprints/test_client.py @@ -0,0 +1,215 @@ +"""Unit tests for the Blueprints HTTP API client.""" + +import pathlib +import zipfile +import io +import datetime +from unittest.mock import AsyncMock, MagicMock + +import httpx +import pytest + +import enapter + + +@pytest.fixture +def mock_httpx_client(): + """Fixture to provide a mocked httpx.AsyncClient.""" + return MagicMock(spec=httpx.AsyncClient) + + +@pytest.fixture +def client(mock_httpx_client): + """Fixture to provide a Blueprints API client with a mocked internal client.""" + return enapter.http.api.blueprints.Client(client=mock_httpx_client) + + +@pytest.mark.asyncio +async def test_get_blueprint(client, mock_httpx_client): + """Test getting a blueprint by ID.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "blueprint": { + "id": "bp_123", + "created_at": "2024-04-01T12:00:00+00:00" + } + } + mock_httpx_client.get = AsyncMock(return_value=mock_response) + + blueprint = await client.get("bp_123") + + assert blueprint.id == "bp_123" + assert blueprint.created_at == datetime.datetime.fromisoformat("2024-04-01T12:00:00+00:00") + mock_httpx_client.get.assert_called_once_with("v3/blueprints/bp_123") + + +@pytest.mark.asyncio +async def test_upload_data(client, mock_httpx_client): + """Test uploading raw data as a blueprint.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "blueprint": { + "id": "bp_new", + "created_at": "2024-04-01T12:00:00+00:00" + } + } + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + data = b"blueprint content" + blueprint = await client.upload(data) + + assert blueprint.id == "bp_new" + assert blueprint.created_at == datetime.datetime.fromisoformat("2024-04-01T12:00:00+00:00") + mock_httpx_client.post.assert_called_once_with("v3/blueprints/upload", content=data) + + +@pytest.mark.asyncio +async def test_upload_file(client, mock_httpx_client, tmp_path): + """Test uploading a blueprint file.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "blueprint": { + "id": "bp_file", + "created_at": "2024-04-01T12:00:00+00:00" + } + } + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + file_path = tmp_path / "manifest.yaml" + file_path.write_bytes(b"manifest content") + + blueprint = await client.upload_file(file_path) + + assert blueprint.id == "bp_file" + mock_httpx_client.post.assert_called_once_with("v3/blueprints/upload", content=b"manifest content") + + +@pytest.mark.asyncio +async def test_upload_directory(client, mock_httpx_client, tmp_path): + """Test uploading a blueprint directory.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "blueprint": { + "id": "bp_dir", + "created_at": "2024-04-01T12:00:00+00:00" + } + } + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + # Setup a directory with a file + dir_path = tmp_path / "blueprint" + dir_path.mkdir() + file_path = dir_path / "manifest.yaml" + file_path.write_bytes(b"manifest content") + + blueprint = await client.upload_directory(dir_path) + + assert blueprint.id == "bp_dir" + + # Verify a zip file was sent + mock_httpx_client.post.assert_called_once() + call_args = mock_httpx_client.post.call_args + assert call_args[0][0] == "v3/blueprints/upload" + sent_data = call_args[1]["content"] + + # Verify it's a valid zip file containing our file + with zipfile.ZipFile(io.BytesIO(sent_data)) as zf: + assert "manifest.yaml" in zf.namelist() + assert zf.read("manifest.yaml") == b"manifest content" + + +@pytest.mark.asyncio +async def test_download(client, mock_httpx_client): + """Test downloading a blueprint.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.content = b"zip content" + mock_httpx_client.get = AsyncMock(return_value=mock_response) + + data = await client.download("bp_123") + + assert data == b"zip content" + mock_httpx_client.get.assert_called_once_with( + "v3/blueprints/bp_123/zip", params={"view": "ORIGINAL"} + ) + + +@pytest.mark.asyncio +async def test_validate_success(client, mock_httpx_client): + """Test successful validation.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = {} + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + await client.validate(b"blueprint content") + + mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"blueprint content") + + +@pytest.mark.asyncio +async def test_validate_error(client, mock_httpx_client): + """Test validation with errors.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "validation_errors": ["Invalid manifest", "Missing field"] + } + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + with pytest.raises(enapter.http.api.MultiError) as exc_info: + await client.validate(b"blueprint content") + + errors = exc_info.value.errors + assert len(errors) == 2 + assert errors[0].message == "Invalid manifest" + assert errors[1].message == "Missing field" + mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"blueprint content") + + +@pytest.mark.asyncio +async def test_validate_file(client, mock_httpx_client, tmp_path): + """Test validating a blueprint file.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = {} + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + file_path = tmp_path / "manifest.yaml" + file_path.write_bytes(b"manifest content") + + await client.validate_file(file_path) + + mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"manifest content") + + +@pytest.mark.asyncio +async def test_validate_directory(client, mock_httpx_client, tmp_path): + """Test validating a blueprint directory.""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = {} + mock_httpx_client.post = AsyncMock(return_value=mock_response) + + # Setup a directory with a file + dir_path = tmp_path / "blueprint" + dir_path.mkdir() + file_path = dir_path / "manifest.yaml" + file_path.write_bytes(b"manifest content") + + await client.validate_directory(dir_path) + + # Verify a zip file was sent + mock_httpx_client.post.assert_called_once() + call_args = mock_httpx_client.post.call_args + assert call_args[0][0] == "v3/blueprints/validate" + sent_data = call_args[1]["content"] + + # Verify it's a valid zip file containing our file + with zipfile.ZipFile(io.BytesIO(sent_data)) as zf: + assert "manifest.yaml" in zf.namelist() + assert zf.read("manifest.yaml") == b"manifest content" From d4b94b5a63ee6cac2a75004470d03f247f6e7da7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:24:49 +0000 Subject: [PATCH 2/3] Add unit tests for the Blueprint API Client Added a comprehensive test suite for `enapter.http.api.blueprints.Client` covering get, upload (data/file/directory), download, and validation methods. Mocks `httpx.AsyncClient` responses to verify request structures and data parsing without making external network requests. Also formats with `black`. Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- .../test_api/test_blueprints/test_client.py | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/unit/test_http/test_api/test_blueprints/test_client.py b/tests/unit/test_http/test_api/test_blueprints/test_client.py index 8651b55..1fd01f5 100644 --- a/tests/unit/test_http/test_api/test_blueprints/test_client.py +++ b/tests/unit/test_http/test_api/test_blueprints/test_client.py @@ -1,9 +1,9 @@ """Unit tests for the Blueprints HTTP API client.""" +import datetime +import io import pathlib import zipfile -import io -import datetime from unittest.mock import AsyncMock, MagicMock import httpx @@ -30,17 +30,16 @@ async def test_get_blueprint(client, mock_httpx_client): mock_response = MagicMock(spec=httpx.Response) mock_response.status_code = 200 mock_response.json.return_value = { - "blueprint": { - "id": "bp_123", - "created_at": "2024-04-01T12:00:00+00:00" - } + "blueprint": {"id": "bp_123", "created_at": "2024-04-01T12:00:00+00:00"} } mock_httpx_client.get = AsyncMock(return_value=mock_response) blueprint = await client.get("bp_123") assert blueprint.id == "bp_123" - assert blueprint.created_at == datetime.datetime.fromisoformat("2024-04-01T12:00:00+00:00") + assert blueprint.created_at == datetime.datetime.fromisoformat( + "2024-04-01T12:00:00+00:00" + ) mock_httpx_client.get.assert_called_once_with("v3/blueprints/bp_123") @@ -50,10 +49,7 @@ async def test_upload_data(client, mock_httpx_client): mock_response = MagicMock(spec=httpx.Response) mock_response.status_code = 200 mock_response.json.return_value = { - "blueprint": { - "id": "bp_new", - "created_at": "2024-04-01T12:00:00+00:00" - } + "blueprint": {"id": "bp_new", "created_at": "2024-04-01T12:00:00+00:00"} } mock_httpx_client.post = AsyncMock(return_value=mock_response) @@ -61,7 +57,9 @@ async def test_upload_data(client, mock_httpx_client): blueprint = await client.upload(data) assert blueprint.id == "bp_new" - assert blueprint.created_at == datetime.datetime.fromisoformat("2024-04-01T12:00:00+00:00") + assert blueprint.created_at == datetime.datetime.fromisoformat( + "2024-04-01T12:00:00+00:00" + ) mock_httpx_client.post.assert_called_once_with("v3/blueprints/upload", content=data) @@ -71,10 +69,7 @@ async def test_upload_file(client, mock_httpx_client, tmp_path): mock_response = MagicMock(spec=httpx.Response) mock_response.status_code = 200 mock_response.json.return_value = { - "blueprint": { - "id": "bp_file", - "created_at": "2024-04-01T12:00:00+00:00" - } + "blueprint": {"id": "bp_file", "created_at": "2024-04-01T12:00:00+00:00"} } mock_httpx_client.post = AsyncMock(return_value=mock_response) @@ -84,7 +79,9 @@ async def test_upload_file(client, mock_httpx_client, tmp_path): blueprint = await client.upload_file(file_path) assert blueprint.id == "bp_file" - mock_httpx_client.post.assert_called_once_with("v3/blueprints/upload", content=b"manifest content") + mock_httpx_client.post.assert_called_once_with( + "v3/blueprints/upload", content=b"manifest content" + ) @pytest.mark.asyncio @@ -93,10 +90,7 @@ async def test_upload_directory(client, mock_httpx_client, tmp_path): mock_response = MagicMock(spec=httpx.Response) mock_response.status_code = 200 mock_response.json.return_value = { - "blueprint": { - "id": "bp_dir", - "created_at": "2024-04-01T12:00:00+00:00" - } + "blueprint": {"id": "bp_dir", "created_at": "2024-04-01T12:00:00+00:00"} } mock_httpx_client.post = AsyncMock(return_value=mock_response) @@ -148,7 +142,9 @@ async def test_validate_success(client, mock_httpx_client): await client.validate(b"blueprint content") - mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"blueprint content") + mock_httpx_client.post.assert_called_once_with( + "v3/blueprints/validate", content=b"blueprint content" + ) @pytest.mark.asyncio @@ -168,7 +164,9 @@ async def test_validate_error(client, mock_httpx_client): assert len(errors) == 2 assert errors[0].message == "Invalid manifest" assert errors[1].message == "Missing field" - mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"blueprint content") + mock_httpx_client.post.assert_called_once_with( + "v3/blueprints/validate", content=b"blueprint content" + ) @pytest.mark.asyncio @@ -184,7 +182,9 @@ async def test_validate_file(client, mock_httpx_client, tmp_path): await client.validate_file(file_path) - mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"manifest content") + mock_httpx_client.post.assert_called_once_with( + "v3/blueprints/validate", content=b"manifest content" + ) @pytest.mark.asyncio From 454f10035a6ed4311af1e22850888fd7132ce2a9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:38:47 +0000 Subject: [PATCH 3/3] Fix Pyflakes linting error in Blueprint API tests Removed unused `pathlib` import from `tests/unit/test_http/test_api/test_blueprints/test_client.py` which was causing the CI check (`make lint-pyflakes`) to fail. Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --- tests/unit/test_http/test_api/test_blueprints/test_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_http/test_api/test_blueprints/test_client.py b/tests/unit/test_http/test_api/test_blueprints/test_client.py index 1fd01f5..10fb69b 100644 --- a/tests/unit/test_http/test_api/test_blueprints/test_client.py +++ b/tests/unit/test_http/test_api/test_blueprints/test_client.py @@ -2,7 +2,6 @@ import datetime import io -import pathlib import zipfile from unittest.mock import AsyncMock, MagicMock