Skip to content

Commit 44363ef

Browse files
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>
1 parent c311b04 commit 44363ef

2 files changed

Lines changed: 215 additions & 0 deletions

File tree

tests/unit/test_http/test_api/test_blueprints/__init__.py

Whitespace-only changes.
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
"""Unit tests for the Blueprints HTTP API client."""
2+
3+
import pathlib
4+
import zipfile
5+
import io
6+
import datetime
7+
from unittest.mock import AsyncMock, MagicMock
8+
9+
import httpx
10+
import pytest
11+
12+
import enapter
13+
14+
15+
@pytest.fixture
16+
def mock_httpx_client():
17+
"""Fixture to provide a mocked httpx.AsyncClient."""
18+
return MagicMock(spec=httpx.AsyncClient)
19+
20+
21+
@pytest.fixture
22+
def client(mock_httpx_client):
23+
"""Fixture to provide a Blueprints API client with a mocked internal client."""
24+
return enapter.http.api.blueprints.Client(client=mock_httpx_client)
25+
26+
27+
@pytest.mark.asyncio
28+
async def test_get_blueprint(client, mock_httpx_client):
29+
"""Test getting a blueprint by ID."""
30+
mock_response = MagicMock(spec=httpx.Response)
31+
mock_response.status_code = 200
32+
mock_response.json.return_value = {
33+
"blueprint": {
34+
"id": "bp_123",
35+
"created_at": "2024-04-01T12:00:00+00:00"
36+
}
37+
}
38+
mock_httpx_client.get = AsyncMock(return_value=mock_response)
39+
40+
blueprint = await client.get("bp_123")
41+
42+
assert blueprint.id == "bp_123"
43+
assert blueprint.created_at == datetime.datetime.fromisoformat("2024-04-01T12:00:00+00:00")
44+
mock_httpx_client.get.assert_called_once_with("v3/blueprints/bp_123")
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_upload_data(client, mock_httpx_client):
49+
"""Test uploading raw data as a blueprint."""
50+
mock_response = MagicMock(spec=httpx.Response)
51+
mock_response.status_code = 200
52+
mock_response.json.return_value = {
53+
"blueprint": {
54+
"id": "bp_new",
55+
"created_at": "2024-04-01T12:00:00+00:00"
56+
}
57+
}
58+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
59+
60+
data = b"blueprint content"
61+
blueprint = await client.upload(data)
62+
63+
assert blueprint.id == "bp_new"
64+
assert blueprint.created_at == datetime.datetime.fromisoformat("2024-04-01T12:00:00+00:00")
65+
mock_httpx_client.post.assert_called_once_with("v3/blueprints/upload", content=data)
66+
67+
68+
@pytest.mark.asyncio
69+
async def test_upload_file(client, mock_httpx_client, tmp_path):
70+
"""Test uploading a blueprint file."""
71+
mock_response = MagicMock(spec=httpx.Response)
72+
mock_response.status_code = 200
73+
mock_response.json.return_value = {
74+
"blueprint": {
75+
"id": "bp_file",
76+
"created_at": "2024-04-01T12:00:00+00:00"
77+
}
78+
}
79+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
80+
81+
file_path = tmp_path / "manifest.yaml"
82+
file_path.write_bytes(b"manifest content")
83+
84+
blueprint = await client.upload_file(file_path)
85+
86+
assert blueprint.id == "bp_file"
87+
mock_httpx_client.post.assert_called_once_with("v3/blueprints/upload", content=b"manifest content")
88+
89+
90+
@pytest.mark.asyncio
91+
async def test_upload_directory(client, mock_httpx_client, tmp_path):
92+
"""Test uploading a blueprint directory."""
93+
mock_response = MagicMock(spec=httpx.Response)
94+
mock_response.status_code = 200
95+
mock_response.json.return_value = {
96+
"blueprint": {
97+
"id": "bp_dir",
98+
"created_at": "2024-04-01T12:00:00+00:00"
99+
}
100+
}
101+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
102+
103+
# Setup a directory with a file
104+
dir_path = tmp_path / "blueprint"
105+
dir_path.mkdir()
106+
file_path = dir_path / "manifest.yaml"
107+
file_path.write_bytes(b"manifest content")
108+
109+
blueprint = await client.upload_directory(dir_path)
110+
111+
assert blueprint.id == "bp_dir"
112+
113+
# Verify a zip file was sent
114+
mock_httpx_client.post.assert_called_once()
115+
call_args = mock_httpx_client.post.call_args
116+
assert call_args[0][0] == "v3/blueprints/upload"
117+
sent_data = call_args[1]["content"]
118+
119+
# Verify it's a valid zip file containing our file
120+
with zipfile.ZipFile(io.BytesIO(sent_data)) as zf:
121+
assert "manifest.yaml" in zf.namelist()
122+
assert zf.read("manifest.yaml") == b"manifest content"
123+
124+
125+
@pytest.mark.asyncio
126+
async def test_download(client, mock_httpx_client):
127+
"""Test downloading a blueprint."""
128+
mock_response = MagicMock(spec=httpx.Response)
129+
mock_response.status_code = 200
130+
mock_response.content = b"zip content"
131+
mock_httpx_client.get = AsyncMock(return_value=mock_response)
132+
133+
data = await client.download("bp_123")
134+
135+
assert data == b"zip content"
136+
mock_httpx_client.get.assert_called_once_with(
137+
"v3/blueprints/bp_123/zip", params={"view": "ORIGINAL"}
138+
)
139+
140+
141+
@pytest.mark.asyncio
142+
async def test_validate_success(client, mock_httpx_client):
143+
"""Test successful validation."""
144+
mock_response = MagicMock(spec=httpx.Response)
145+
mock_response.status_code = 200
146+
mock_response.json.return_value = {}
147+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
148+
149+
await client.validate(b"blueprint content")
150+
151+
mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"blueprint content")
152+
153+
154+
@pytest.mark.asyncio
155+
async def test_validate_error(client, mock_httpx_client):
156+
"""Test validation with errors."""
157+
mock_response = MagicMock(spec=httpx.Response)
158+
mock_response.status_code = 200
159+
mock_response.json.return_value = {
160+
"validation_errors": ["Invalid manifest", "Missing field"]
161+
}
162+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
163+
164+
with pytest.raises(enapter.http.api.MultiError) as exc_info:
165+
await client.validate(b"blueprint content")
166+
167+
errors = exc_info.value.errors
168+
assert len(errors) == 2
169+
assert errors[0].message == "Invalid manifest"
170+
assert errors[1].message == "Missing field"
171+
mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"blueprint content")
172+
173+
174+
@pytest.mark.asyncio
175+
async def test_validate_file(client, mock_httpx_client, tmp_path):
176+
"""Test validating a blueprint file."""
177+
mock_response = MagicMock(spec=httpx.Response)
178+
mock_response.status_code = 200
179+
mock_response.json.return_value = {}
180+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
181+
182+
file_path = tmp_path / "manifest.yaml"
183+
file_path.write_bytes(b"manifest content")
184+
185+
await client.validate_file(file_path)
186+
187+
mock_httpx_client.post.assert_called_once_with("v3/blueprints/validate", content=b"manifest content")
188+
189+
190+
@pytest.mark.asyncio
191+
async def test_validate_directory(client, mock_httpx_client, tmp_path):
192+
"""Test validating a blueprint directory."""
193+
mock_response = MagicMock(spec=httpx.Response)
194+
mock_response.status_code = 200
195+
mock_response.json.return_value = {}
196+
mock_httpx_client.post = AsyncMock(return_value=mock_response)
197+
198+
# Setup a directory with a file
199+
dir_path = tmp_path / "blueprint"
200+
dir_path.mkdir()
201+
file_path = dir_path / "manifest.yaml"
202+
file_path.write_bytes(b"manifest content")
203+
204+
await client.validate_directory(dir_path)
205+
206+
# Verify a zip file was sent
207+
mock_httpx_client.post.assert_called_once()
208+
call_args = mock_httpx_client.post.call_args
209+
assert call_args[0][0] == "v3/blueprints/validate"
210+
sent_data = call_args[1]["content"]
211+
212+
# Verify it's a valid zip file containing our file
213+
with zipfile.ZipFile(io.BytesIO(sent_data)) as zf:
214+
assert "manifest.yaml" in zf.namelist()
215+
assert zf.read("manifest.yaml") == b"manifest content"

0 commit comments

Comments
 (0)