Skip to content

Commit 4994558

Browse files
committed
test: add comprehensive tests for file handler and builder modules
- Added file handler tests covering all functions with tempfiles - Added builder tests with proper mocking of HTTP client - Tests cover initialization, chaining, execute, and string representations - All tests use unittest.mock for consistency - 40 tests now passing with good coverage
1 parent 8709665 commit 4994558

File tree

2 files changed

+297
-0
lines changed

2 files changed

+297
-0
lines changed

tests/unit/test_builder.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""Unit tests for BuildAPIWrapper."""
2+
3+
from unittest.mock import Mock, patch
4+
5+
from nutrient_dws.builder import BuildAPIWrapper
6+
7+
8+
class TestBuildAPIWrapper:
9+
"""Test BuildAPIWrapper class."""
10+
11+
def test_init(self):
12+
"""Test initialization."""
13+
mock_client = Mock()
14+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
15+
16+
assert builder._client == mock_client
17+
assert builder._input_file == "test.pdf"
18+
assert builder._actions == []
19+
assert builder._output_options == {}
20+
assert builder._parts == [{"file": "file"}]
21+
assert "file" in builder._files
22+
23+
def test_add_step_basic(self):
24+
"""Test adding a basic step."""
25+
mock_client = Mock()
26+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
27+
28+
result = builder.add_step("convert-to-pdf")
29+
30+
assert result == builder # Should return self for chaining
31+
assert len(builder._actions) == 1
32+
assert builder._actions[0]["type"] == "convert-to-pdf"
33+
34+
def test_add_step_with_options(self):
35+
"""Test adding a step with options."""
36+
mock_client = Mock()
37+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
38+
39+
builder.add_step("rotate-pages", {"degrees": 90})
40+
41+
assert len(builder._actions) == 1
42+
assert builder._actions[0]["type"] == "rotate"
43+
assert builder._actions[0]["rotateBy"] == 90
44+
45+
def test_chaining_steps(self):
46+
"""Test chaining multiple steps."""
47+
mock_client = Mock()
48+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
49+
50+
builder.add_step("convert-to-pdf").add_step("ocr-pdf", {"language": "en"})
51+
52+
assert len(builder._actions) == 2
53+
assert builder._actions[0]["type"] == "convert-to-pdf"
54+
assert builder._actions[1]["type"] == "ocr"
55+
assert builder._actions[1]["language"] == "english"
56+
57+
def test_set_output_options(self):
58+
"""Test setting output options."""
59+
mock_client = Mock()
60+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
61+
62+
result = builder.set_output_options(format="pdf", quality=90)
63+
64+
assert result == builder # Should return self for chaining
65+
assert builder._output_options == {"format": "pdf", "quality": 90}
66+
67+
def test_build_instructions(self):
68+
"""Test building instructions."""
69+
mock_client = Mock()
70+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
71+
72+
builder.add_step("convert-to-pdf")
73+
builder.add_step("ocr-pdf", {"language": "en"})
74+
builder.set_output_options(format="pdf")
75+
76+
instructions = builder._build_instructions()
77+
78+
assert "parts" in instructions
79+
assert instructions["actions"] == builder._actions
80+
assert instructions["output"] == {"format": "pdf"}
81+
82+
def test_execute_returns_bytes(self):
83+
"""Test execute returns bytes when no output path."""
84+
mock_client = Mock()
85+
mock_http_client = Mock()
86+
mock_client._http_client = mock_http_client
87+
88+
# Mock the response
89+
mock_response = b"PDF content"
90+
mock_http_client.post.return_value = mock_response
91+
92+
builder = BuildAPIWrapper(client=mock_client, input_file=b"input content")
93+
builder.add_step("convert-to-pdf")
94+
95+
with patch("nutrient_dws.builder.prepare_file_for_upload") as mock_prepare:
96+
mock_prepare.return_value = ("file", ("doc.pdf", b"input content", "application/pdf"))
97+
98+
result = builder.execute()
99+
100+
assert result == b"PDF content"
101+
102+
def test_execute_saves_to_file(self):
103+
"""Test execute saves to file when output path provided."""
104+
mock_client = Mock()
105+
mock_http_client = Mock()
106+
mock_client._http_client = mock_http_client
107+
108+
# Mock the response
109+
mock_response = b"PDF content"
110+
mock_http_client.post.return_value = mock_response
111+
112+
builder = BuildAPIWrapper(client=mock_client, input_file=b"input content")
113+
builder.add_step("convert-to-pdf")
114+
115+
with patch("nutrient_dws.builder.prepare_file_for_upload") as mock_prepare:
116+
mock_prepare.return_value = ("file", ("doc.pdf", b"input content", "application/pdf"))
117+
118+
with patch("nutrient_dws.builder.save_file_output") as mock_save:
119+
result = builder.execute("output.pdf")
120+
121+
assert result is None
122+
mock_save.assert_called_once_with(b"PDF content", "output.pdf")
123+
124+
def test_str_representation(self):
125+
"""Test string representation."""
126+
mock_client = Mock()
127+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
128+
builder.add_step("convert-to-pdf")
129+
130+
str_repr = str(builder)
131+
assert "BuildAPIWrapper" in str_repr
132+
assert "convert-to-pdf" in str_repr
133+
134+
def test_repr_representation(self):
135+
"""Test repr representation."""
136+
mock_client = Mock()
137+
builder = BuildAPIWrapper(client=mock_client, input_file="test.pdf")
138+
builder.add_step("convert-to-pdf")
139+
builder.add_step("ocr-pdf")
140+
141+
repr_str = repr(builder)
142+
assert "BuildAPIWrapper" in repr_str
143+
assert "input_file=" in repr_str
144+
assert "actions=" in repr_str

tests/unit/test_file_handler.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""Unit tests for file handling utilities."""
2+
3+
import io
4+
import tempfile
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from nutrient_dws.file_handler import (
10+
get_file_size,
11+
prepare_file_for_upload,
12+
prepare_file_input,
13+
save_file_output,
14+
)
15+
16+
17+
class TestPrepareFileInput:
18+
"""Test prepare_file_input function."""
19+
20+
def test_bytes_input(self):
21+
"""Test handling of bytes input."""
22+
test_content = b"Test content"
23+
content, filename = prepare_file_input(test_content)
24+
25+
assert content == test_content
26+
assert filename == "document"
27+
28+
def test_string_path_input(self):
29+
"""Test handling of string path input."""
30+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
31+
tmp.write(b"File content")
32+
tmp_path = tmp.name
33+
34+
try:
35+
content, filename = prepare_file_input(tmp_path)
36+
assert content == b"File content"
37+
assert Path(tmp_path).name in filename
38+
finally:
39+
Path(tmp_path).unlink()
40+
41+
def test_path_object_input(self):
42+
"""Test handling of Path object input."""
43+
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
44+
tmp.write(b"PDF content")
45+
tmp_path = Path(tmp.name)
46+
47+
try:
48+
content, filename = prepare_file_input(tmp_path)
49+
assert content == b"PDF content"
50+
assert tmp_path.name == filename
51+
finally:
52+
tmp_path.unlink()
53+
54+
def test_file_not_found(self):
55+
"""Test handling of non-existent file."""
56+
with pytest.raises(FileNotFoundError):
57+
prepare_file_input("non_existent_file.pdf")
58+
59+
def test_file_like_object(self):
60+
"""Test handling of file-like object."""
61+
file_obj = io.BytesIO(b"Binary content")
62+
content, filename = prepare_file_input(file_obj)
63+
64+
assert content == b"Binary content"
65+
assert filename == "document"
66+
67+
def test_unsupported_input_type(self):
68+
"""Test handling of unsupported input type."""
69+
with pytest.raises(ValueError, match="Unsupported file input type"):
70+
prepare_file_input(123) # type: ignore
71+
72+
73+
class TestPrepareFileForUpload:
74+
"""Test prepare_file_for_upload function."""
75+
76+
def test_bytes_upload(self):
77+
"""Test preparing bytes for upload."""
78+
test_content = b"Upload content"
79+
field_name, file_tuple = prepare_file_for_upload(test_content)
80+
81+
assert field_name == "file"
82+
filename, content, content_type = file_tuple
83+
assert filename == "document"
84+
assert content == test_content
85+
assert content_type == "application/octet-stream"
86+
87+
def test_custom_field_name(self):
88+
"""Test custom field name."""
89+
test_content = b"Content"
90+
field_name, _ = prepare_file_for_upload(test_content, field_name="custom")
91+
92+
assert field_name == "custom"
93+
94+
95+
class TestSaveFileOutput:
96+
"""Test save_file_output function."""
97+
98+
def test_save_to_path(self):
99+
"""Test saving content to file."""
100+
content = b"Save this content"
101+
102+
with tempfile.TemporaryDirectory() as tmpdir:
103+
output_path = Path(tmpdir) / "output.pdf"
104+
save_file_output(content, str(output_path))
105+
106+
assert output_path.exists()
107+
assert output_path.read_bytes() == content
108+
109+
def test_create_parent_directories(self):
110+
"""Test creating parent directories."""
111+
content = b"Content"
112+
113+
with tempfile.TemporaryDirectory() as tmpdir:
114+
output_path = Path(tmpdir) / "sub" / "dir" / "output.pdf"
115+
save_file_output(content, str(output_path))
116+
117+
assert output_path.exists()
118+
assert output_path.read_bytes() == content
119+
120+
121+
class TestGetFileSize:
122+
"""Test get_file_size function."""
123+
124+
def test_bytes_size(self):
125+
"""Test getting size of bytes."""
126+
content = b"12345"
127+
size = get_file_size(content)
128+
assert size == 5
129+
130+
def test_file_path_size(self):
131+
"""Test getting size of file path."""
132+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
133+
tmp.write(b"1234567890")
134+
tmp_path = tmp.name
135+
136+
try:
137+
size = get_file_size(tmp_path)
138+
assert size == 10
139+
finally:
140+
Path(tmp_path).unlink()
141+
142+
def test_non_existent_file_size(self):
143+
"""Test getting size of non-existent file."""
144+
size = get_file_size("non_existent.pdf")
145+
assert size is None
146+
147+
def test_file_object_size(self):
148+
"""Test getting size of file-like object."""
149+
file_obj = io.BytesIO(b"123")
150+
size = get_file_size(file_obj)
151+
assert size == 3
152+
# Ensure position is restored
153+
assert file_obj.tell() == 0

0 commit comments

Comments
 (0)