Skip to content

Commit 2ab3cb4

Browse files
committed
Merge relevant new tests from serialization branch
1 parent db708c6 commit 2ab3cb4

4 files changed

Lines changed: 104 additions & 5 deletions

File tree

src/fractured_json/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ def list_options(self) -> dict[str, dict[str, object | str | list | bool]]:
178178

179179
def get(self, name: str) -> int | bool | str | NativeEnum:
180180
"""Getter for an option that calls the .NET class."""
181+
if name not in self._properties:
182+
msg = f"Unknown option '{name}'"
183+
raise AttributeError(msg)
181184
prop = self._properties[name]["prop"]
182185
if self._properties[name]["is_enum"]:
183186
native_value = prop.GetValue(self._dotnet_instance)
@@ -226,7 +229,7 @@ def set(self, name: str, value: int | bool | str | NativeEnum) -> None: # noqa:
226229
"""Setter for an option that calls the .NET class."""
227230
if name not in self._properties:
228231
msg = f"Unknown option '{name}'"
229-
raise KeyError(msg)
232+
raise AttributeError(msg)
230233

231234
prop = self._properties[name]["prop"]
232235
try:
@@ -254,7 +257,8 @@ def __setattr__(self, name: str, value: int | bool | str | NativeEnum) -> None:
254257
try:
255258
self.set(name, value)
256259
except AttributeError:
257-
object.__setattr__(self, name, value)
260+
msg = f"{type(self).__name__} has no attribute '{name}'"
261+
raise AttributeError(msg) from None
258262

259263

260264
class Formatter:
@@ -279,12 +283,12 @@ def options(self, value: FracturedJsonOptions) -> None:
279283
prop = FormatterType.GetProperty("Options")
280284
prop.SetValue(self._dotnet_instance, value._dotnet_instance)
281285

282-
def reformat(self, json_text: str) -> str:
286+
def reformat(self, json_text: str, starting_depth: int = 0) -> str:
283287
"""Reformat a JSON string and return the formatted result."""
284288
if not isinstance(json_text, str):
285289
msg = "json_text must be a str"
286290
raise TypeError(msg)
287-
result = self._dotnet_instance.Reformat(String(json_text))
291+
result = self._dotnet_instance.Reformat(String(json_text), Int32(starting_depth))
288292
return str(result)
289293

290294
def minify(self, json_text: str) -> str:

tests/test_console_script.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import re
2+
from io import StringIO
23
from pathlib import Path
4+
from shutil import copy
35

46
import pytest
57

@@ -139,6 +141,28 @@ def test_output(script_runner, tmp_path):
139141
assert tmp_file.read_text() == '{ "bools": {"true": true, "false": false} }\n'
140142

141143

144+
def test_in_place(script_runner, tmp_path):
145+
tmp_file = tmp_path / "test.json"
146+
copy(Path("tests/data/test-bool.json"), tmp_file)
147+
ret = script_runner.run(
148+
["fractured-json", "--in-place", tmp_file],
149+
)
150+
assert ret.stderr == ""
151+
assert ret.success
152+
assert tmp_file.read_text() == '{ "bools": {"true": true, "false": false} }\n'
153+
154+
timestamp = tmp_file.stat().st_mtime_ns
155+
156+
ret = script_runner.run(
157+
["fractured-json", "--in-place", tmp_file],
158+
)
159+
assert ret.stderr == ""
160+
assert ret.success
161+
assert tmp_file.read_text() == '{ "bools": {"true": true, "false": false} }\n'
162+
163+
assert timestamp == tmp_file.stat().st_mtime_ns
164+
165+
142166
def test_output_mismatched_number_of_files(script_runner):
143167
ret = script_runner.run(
144168
[
@@ -152,3 +176,21 @@ def test_output_mismatched_number_of_files(script_runner):
152176
)
153177
assert ret.stderr == "fractured-json: the numbers of input and output file names do not match\n"
154178
assert ret.returncode == 1
179+
180+
181+
def test_pipe_stdin(script_runner):
182+
json_input_fh = StringIO(Path("tests/data/test-bool.json").read_text())
183+
ret = script_runner.run(["fractured-json", "-"], stdin=json_input_fh)
184+
assert ret.success
185+
assert ret.stderr == ""
186+
assert ret.success
187+
assert ret.stdout == '{ "bools": {"true": true, "false": false} }\n'
188+
189+
190+
def test_missing_file(script_runner, tmp_path):
191+
ret = script_runner.run(
192+
["fractured-json", "file-does-not-exist.json"],
193+
)
194+
assert not ret.success
195+
assert "[Errno 2] No such file or directory: 'file-does-not-exist.json'" in ret.stderr
196+
assert ret.stdout == ""

tests/test_formatter.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,15 @@ def test_minify():
7373
assert test_output == ref_output
7474

7575

76+
def test_depth():
77+
json_input = Path("tests/data/test-bool.json").read_text()
78+
formatter = Formatter()
79+
test_output = formatter.reformat(json_input, 2)
80+
assert test_output == ' { "bools": {"true": true, "false": false} }\n'
81+
82+
7683
def test_exceptions():
77-
with pytest.raises(KeyError, match="Unknown option 'non_existent_option'"):
84+
with pytest.raises(AttributeError, match="Unknown option 'non_existent_option'"):
7885
_ = FracturedJsonOptions(non_existent_option=True)
7986

8087
with pytest.raises(
@@ -110,6 +117,19 @@ def test_exceptions():
110117
with pytest.raises(TypeError, match="Must be callable"):
111118
formatter.string_length_func = 123 # type: ignore[assignment]
112119

120+
options = FracturedJsonOptions()
121+
with pytest.raises(
122+
AttributeError,
123+
match="FracturedJsonOptions has no attribute 'invalid'",
124+
):
125+
_ = options.invalid
126+
127+
with pytest.raises(
128+
AttributeError,
129+
match="FracturedJsonOptions has no attribute 'invalid'",
130+
):
131+
options.invalid = 0
132+
113133

114134
def test_dll_missing(path_is_file_fails): # noqa: ARG001
115135
if "fractured_json" in sys.modules:
@@ -142,3 +162,26 @@ def double_len(s: str) -> int:
142162
getter = formatter.string_length_func
143163
assert callable(getter)
144164
assert getter("abc") == 6
165+
166+
167+
def test_formatter_options():
168+
opts = FracturedJsonOptions(
169+
max_total_line_length=80,
170+
indent_spaces=3,
171+
comment_policy="REMOVE",
172+
table_comma_placement="BEFORE_PADDING_EXCEPT_NUMBERS",
173+
number_list_alignment="LEFT",
174+
prefix_string="::",
175+
use_tab_to_indent=False,
176+
)
177+
178+
formatter = Formatter()
179+
formatter.options = opts
180+
181+
got = formatter.options
182+
assert got.max_total_line_length == 80
183+
assert got.indent_spaces == 3
184+
assert got.comment_policy.name == "REMOVE"
185+
186+
got.max_total_line_length = 100
187+
assert got.max_total_line_length == 100

tests/test_native_enum.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from fractured_json import CommentPolicy, NumberListAlignment
2+
3+
4+
def test_eq_and_hash():
5+
assert CommentPolicy.REMOVE != CommentPolicy.PRESERVE
6+
assert NumberListAlignment.LEFT == 0
7+
a = dict()
8+
a[CommentPolicy.REMOVE] = "test"
9+
assert list(a.keys())[0].name == "REMOVE"
10+
assert list(a.keys())[0].value == 1

0 commit comments

Comments
 (0)