Skip to content

Commit 3a83fc5

Browse files
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>
1 parent c311b04 commit 3a83fc5

3 files changed

Lines changed: 236 additions & 0 deletions

File tree

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

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

0 commit comments

Comments
 (0)