Skip to content

Commit f39abc5

Browse files
committed
refactor: extract models.to_json
1 parent ef559e1 commit f39abc5

File tree

3 files changed

+171
-195
lines changed

3 files changed

+171
-195
lines changed

tests/test_models.py

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
import json
2+
from pathlib import Path
3+
14
import pytest
25

36
from unblob.file_utils import InvalidInputFormat
4-
from unblob.models import Chunk, UnknownChunk
7+
from unblob.models import Chunk, ProcessResult, Task, TaskResult, UnknownChunk, to_json
8+
from unblob.report import (
9+
ChunkReport,
10+
ExtractCommandFailedReport,
11+
FileMagicReport,
12+
HashReport,
13+
StatReport,
14+
)
515

616

717
class TestChunk:
@@ -47,3 +57,157 @@ def test_contains_offset(self, chunk, offset, expected):
4757
def test_validation(self, start_offset, end_offset):
4858
with pytest.raises(InvalidInputFormat):
4959
Chunk(start_offset, end_offset)
60+
61+
62+
class Test_to_json: # noqa: N801
63+
def test_process_result_conversion(self):
64+
task = Task(path=Path("/nonexistent"), depth=0, chunk_id="")
65+
task_result = TaskResult(task)
66+
chunk_id = "test_basic_conversion:id"
67+
68+
task_result.add_report(
69+
StatReport(
70+
path=task.path,
71+
size=384,
72+
is_dir=False,
73+
is_file=True,
74+
is_link=False,
75+
link_target=None,
76+
)
77+
)
78+
task_result.add_report(
79+
FileMagicReport(
80+
magic="Zip archive data, at least v2.0 to extract",
81+
mime_type="application/zip",
82+
)
83+
)
84+
task_result.add_report(
85+
HashReport(
86+
md5="9019fcece2433ad7f12c077e84537a74",
87+
sha1="36998218d8f43b69ef3adcadf2e8979e81eed166",
88+
sha256="7d7ca7e1410b702b0f85d18257aebb964ac34f7fad0a0328d72e765bfcb21118",
89+
)
90+
)
91+
task_result.add_report(
92+
ChunkReport(
93+
chunk_id=chunk_id,
94+
handler_name="zip",
95+
start_offset=0,
96+
end_offset=384,
97+
size=384,
98+
is_encrypted=False,
99+
extraction_reports=[],
100+
)
101+
)
102+
task_result.add_subtask(
103+
Task(
104+
path=Path("/extractions/nonexistent_extract"),
105+
depth=314,
106+
chunk_id=chunk_id,
107+
)
108+
)
109+
110+
json_text = ProcessResult(results=[task_result]).to_json()
111+
112+
# output must be a valid json string
113+
assert isinstance(json_text, str)
114+
115+
# that can be loaded back
116+
decoded_report = json.loads(json_text)
117+
assert decoded_report == [
118+
{
119+
"__typename__": "TaskResult",
120+
"reports": [
121+
{
122+
"__typename__": "StatReport",
123+
"is_dir": False,
124+
"is_file": True,
125+
"is_link": False,
126+
"link_target": None,
127+
"path": "/nonexistent",
128+
"size": 384,
129+
},
130+
{
131+
"__typename__": "FileMagicReport",
132+
"magic": "Zip archive data, at least v2.0 to extract",
133+
"mime_type": "application/zip",
134+
},
135+
{
136+
"__typename__": "HashReport",
137+
"md5": "9019fcece2433ad7f12c077e84537a74",
138+
"sha1": "36998218d8f43b69ef3adcadf2e8979e81eed166",
139+
"sha256": "7d7ca7e1410b702b0f85d18257aebb964ac34f7fad0a0328d72e765bfcb21118",
140+
},
141+
{
142+
"__typename__": "ChunkReport",
143+
"end_offset": 384,
144+
"extraction_reports": [],
145+
"handler_name": "zip",
146+
"chunk_id": "test_basic_conversion:id",
147+
"is_encrypted": False,
148+
"size": 384,
149+
"start_offset": 0,
150+
},
151+
],
152+
"subtasks": [
153+
{
154+
"__typename__": "Task",
155+
"chunk_id": "test_basic_conversion:id",
156+
"depth": 314,
157+
"path": "/extractions/nonexistent_extract",
158+
}
159+
],
160+
"task": {
161+
"__typename__": "Task",
162+
"chunk_id": "",
163+
"depth": 0,
164+
"path": "/nonexistent",
165+
},
166+
},
167+
]
168+
169+
def test_exotic_command_output(self):
170+
report = ExtractCommandFailedReport(
171+
command="dump all bytes",
172+
stdout=bytes(range(256)),
173+
stderr=b"stdout is pretty strange ;)",
174+
exit_code=1,
175+
)
176+
177+
json_text = to_json(report)
178+
179+
decoded_report = json.loads(json_text)
180+
181+
assert decoded_report == {
182+
"__typename__": "ExtractCommandFailedReport",
183+
"command": "dump all bytes",
184+
"exit_code": 1,
185+
"severity": "WARNING",
186+
"stderr": "stdout is pretty strange ;)",
187+
"stdout": (
188+
"b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07"
189+
"\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f"
190+
"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17"
191+
'\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !"#'
192+
"$%&\\'()*+,-./0123456789:;<=>?@AB"
193+
"CDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`a"
194+
"bcdefghijklmnopqrstuvwxyz{|}~\\x7f"
195+
"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87"
196+
"\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f"
197+
"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97"
198+
"\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f"
199+
"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7"
200+
"\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"
201+
"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7"
202+
"\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf"
203+
"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7"
204+
"\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf"
205+
"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7"
206+
"\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf"
207+
"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7"
208+
"\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef"
209+
"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7"
210+
"\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"
211+
"'"
212+
),
213+
}

tests/test_report.py

Lines changed: 0 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from unblob.processing import ExtractionConfig, process_file
1212
from unblob.report import (
1313
ChunkReport,
14-
ExtractCommandFailedReport,
1514
FileMagicReport,
1615
HashReport,
1716
StatReport,
@@ -49,198 +48,6 @@ def test_process_file_report_output_is_valid_json(
4948
assert len(report)
5049

5150

52-
class Test_ProcessResult_to_json: # noqa: N801
53-
def test_simple_conversion(self):
54-
task = Task(path=Path("/nonexistent"), depth=0, chunk_id="")
55-
task_result = TaskResult(task)
56-
chunk_id = "test_basic_conversion:id"
57-
58-
task_result.add_report(
59-
StatReport(
60-
path=task.path,
61-
size=384,
62-
is_dir=False,
63-
is_file=True,
64-
is_link=False,
65-
link_target=None,
66-
)
67-
)
68-
task_result.add_report(
69-
FileMagicReport(
70-
magic="Zip archive data, at least v2.0 to extract",
71-
mime_type="application/zip",
72-
)
73-
)
74-
task_result.add_report(
75-
HashReport(
76-
md5="9019fcece2433ad7f12c077e84537a74",
77-
sha1="36998218d8f43b69ef3adcadf2e8979e81eed166",
78-
sha256="7d7ca7e1410b702b0f85d18257aebb964ac34f7fad0a0328d72e765bfcb21118",
79-
)
80-
)
81-
task_result.add_report(
82-
ChunkReport(
83-
chunk_id=chunk_id,
84-
handler_name="zip",
85-
start_offset=0,
86-
end_offset=384,
87-
size=384,
88-
is_encrypted=False,
89-
extraction_reports=[],
90-
)
91-
)
92-
task_result.add_subtask(
93-
Task(
94-
path=Path("/extractions/nonexistent_extract"),
95-
depth=314,
96-
chunk_id=chunk_id,
97-
)
98-
)
99-
100-
json_text = ProcessResult(results=[task_result]).to_json()
101-
102-
# output must be a valid json string
103-
assert isinstance(json_text, str)
104-
105-
# that can be loaded back
106-
decoded_report = json.loads(json_text)
107-
assert decoded_report == [
108-
{
109-
"__typename__": "TaskResult",
110-
"reports": [
111-
{
112-
"__typename__": "StatReport",
113-
"is_dir": False,
114-
"is_file": True,
115-
"is_link": False,
116-
"link_target": None,
117-
"path": "/nonexistent",
118-
"size": 384,
119-
},
120-
{
121-
"__typename__": "FileMagicReport",
122-
"magic": "Zip archive data, at least v2.0 to extract",
123-
"mime_type": "application/zip",
124-
},
125-
{
126-
"__typename__": "HashReport",
127-
"md5": "9019fcece2433ad7f12c077e84537a74",
128-
"sha1": "36998218d8f43b69ef3adcadf2e8979e81eed166",
129-
"sha256": "7d7ca7e1410b702b0f85d18257aebb964ac34f7fad0a0328d72e765bfcb21118",
130-
},
131-
{
132-
"__typename__": "ChunkReport",
133-
"end_offset": 384,
134-
"extraction_reports": [],
135-
"handler_name": "zip",
136-
"chunk_id": "test_basic_conversion:id",
137-
"is_encrypted": False,
138-
"size": 384,
139-
"start_offset": 0,
140-
},
141-
],
142-
"subtasks": [
143-
{
144-
"__typename__": "Task",
145-
"chunk_id": "test_basic_conversion:id",
146-
"depth": 314,
147-
"path": "/extractions/nonexistent_extract",
148-
}
149-
],
150-
"task": {
151-
"__typename__": "Task",
152-
"chunk_id": "",
153-
"depth": 0,
154-
"path": "/nonexistent",
155-
},
156-
},
157-
]
158-
159-
def test_exotic_command_output(self):
160-
task = Task(path=Path("/nonexistent"), depth=0, chunk_id="")
161-
task_result = TaskResult(task)
162-
report = ExtractCommandFailedReport(
163-
command="dump all bytes",
164-
stdout=bytes(range(256)),
165-
stderr=b"stdout is pretty strange ;)",
166-
exit_code=1,
167-
)
168-
169-
task_result.add_report(
170-
ChunkReport(
171-
chunk_id="test",
172-
handler_name="fail",
173-
start_offset=0,
174-
end_offset=256,
175-
size=256,
176-
is_encrypted=False,
177-
extraction_reports=[report],
178-
)
179-
)
180-
json_text = ProcessResult(results=[task_result]).to_json()
181-
182-
decoded_report = json.loads(json_text)
183-
184-
assert decoded_report == [
185-
{
186-
"__typename__": "TaskResult",
187-
"reports": [
188-
{
189-
"__typename__": "ChunkReport",
190-
"end_offset": 256,
191-
"extraction_reports": [
192-
{
193-
"__typename__": "ExtractCommandFailedReport",
194-
"command": "dump all bytes",
195-
"exit_code": 1,
196-
"severity": "WARNING",
197-
"stderr": "stdout is pretty strange ;)",
198-
"stdout": (
199-
"b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07"
200-
"\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f"
201-
"\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17"
202-
'\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f !"#'
203-
"$%&\\'()*+,-./0123456789:;<=>?@AB"
204-
"CDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`a"
205-
"bcdefghijklmnopqrstuvwxyz{|}~\\x7f"
206-
"\\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87"
207-
"\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f"
208-
"\\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97"
209-
"\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f"
210-
"\\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7"
211-
"\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf"
212-
"\\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7"
213-
"\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf"
214-
"\\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7"
215-
"\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf"
216-
"\\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7"
217-
"\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf"
218-
"\\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7"
219-
"\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef"
220-
"\\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7"
221-
"\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff"
222-
"'"
223-
),
224-
}
225-
],
226-
"handler_name": "fail",
227-
"chunk_id": "test",
228-
"is_encrypted": False,
229-
"size": 256,
230-
"start_offset": 0,
231-
}
232-
],
233-
"subtasks": [],
234-
"task": {
235-
"__typename__": "Task",
236-
"chunk_id": "",
237-
"depth": 0,
238-
"path": "/nonexistent",
239-
},
240-
}
241-
]
242-
243-
24451
@pytest.fixture
24552
def hello_kitty(tmp_path: Path) -> Path:
24653
"""Generate an input file with 3 unknown chunks and 2 zip files."""

0 commit comments

Comments
 (0)