Skip to content

Commit c5736c9

Browse files
🧪 Add unit tests for HTTP API blueprints (#44)
* Add unit tests for HTTP API blueprints - Create tests for Blueprint data model (from_dto, to_dto, roundtrip) - Create tests for Blueprints Client (get, upload, download, validate) - Cover happy paths and error conditions - Use pytest fixtures and mocks for isolation Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> * Add unit tests for HTTP API blueprints - Implement tests for Blueprint data model and DTO conversions - Implement tests for Blueprints Client methods: get, upload, download, validate - Fix linting issues (black formatting) identified in CI - Address PR feedback regarding test coverage and structure Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> * Fix linting and unused import errors in blueprint tests - Remove unused 'pathlib' and 'Blueprint' imports in test_client.py - Apply black reformatting to test files - Verified pass for black, ruff, isort, and mypy locally Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> * Add thorough zipped content verification in blueprint tests - Update test_upload_directory and test_validate_directory to inspect ZIP bytes - Verify file presence and content in the generated archives - Ensure robust testing of directory zipping logic Co-authored-by: rnovatorov <20299819+rnovatorov@users.noreply.github.com> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent eded498 commit c5736c9

File tree

3 files changed

+255
-0
lines changed

3 files changed

+255
-0
lines changed

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

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

0 commit comments

Comments
 (0)